Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ It is used by the WordPress team for sites and packages.
- [@yardinternet/ts-config](packages/ts-config/README.md)
- [@yardinternet/ts-config-wordpress](packages/ts-config-wordpress/README.md)
- [@yardinternet/vite-config](packages/vite-config/README.md)
- [@yardinternet/wp-scripts-config](packages/wp-scripts-config/README.md)

## 👷‍♀️ Package Development

Expand Down Expand Up @@ -44,14 +45,6 @@ pnpm dep:update # Update all dependencies
pnpm test
```

Added to catch two types of problems:
- **Dependency updates** silently changing rules
- **Our own config edits** disabling or weakening rules

Each package has two test files:
- `config.snapshot.test.js` — snapshots the full config and fails on a change
- `rules.test.js` / `format.test.js` — behavioral tests that lint/format real code snippets

Tests run on pre-push and in GitHub Actions on every pull request.

## 🎨 Formatting & Linting
Expand Down
20 changes: 20 additions & 0 deletions packages/wp-scripts-config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# @yardinternet/wp-scripts-config

The `@wordpress/scripts` webpack config, with our fixes applied.

## Usage

```diff
-const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
+const defaultConfig = require( '@yardinternet/wp-scripts-config' );
```

## Fixes

### `disable-sass-charset`

Sets `sassOptions.charset = false` so Sass stops emitting a BOM that breaks the concatenated CSS. See [WordPress/gutenberg#81382](https://github.com/WordPress/gutenberg/issues/81382).

## Adding a fix

Drop a module in `src/transforms/` that takes a config and returns it, then add it to the `transforms` array in `src/index.js`.
28 changes: 28 additions & 0 deletions packages/wp-scripts-config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@yardinternet/wp-scripts-config",
"version": "1.0.0",
"description": "The @wordpress/scripts webpack config, with Sass charset output disabled",
"main": "src/index.js",
"type": "commonjs",
"scripts": {
"test": "vitest run"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yardinternet/toolkit.git",
"directory": "packages/wp-scripts-config"
},
"author": "",
"license": "ISC",
"peerDependencies": {
"@wordpress/scripts": ">=15"
},
"devDependencies": {
"@wordpress/scripts": "^34.0.0",
"sass": "^1.102.0",
"vitest": "^3.2.6"
}
}
11 changes: 11 additions & 0 deletions packages/wp-scripts-config/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );

// Add all transforms to the array here.
const allTransforms = [ require( './transforms/disable-sass-charset' ) ];

module.exports = allTransforms.reduce(
( config, transform ) => transform( config ),
defaultConfig
Comment on lines +5 to +10
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

/**
* Remove the BOM character from the output of sass-loader.
*
* @see https://github.com/WordPress/gutenberg/issues/81382
*
* @param {Object} config Webpack configuration object.
* @return {Object} Webpack configuration object with the sass-loader charset option disabled.
*/
module.exports = ( config ) => {
for ( const rule of config.module?.rules ?? [] ) {
for ( const use of Array.isArray( rule.use ) ? rule.use : [] ) {
if ( ! use?.loader?.includes?.( 'sass-loader' ) ) {
continue;
}

use.options = {
...use.options,
sassOptions: { ...use.options?.sassOptions, charset: false },
};
}
}

return config;
};
Comment on lines +11 to +26
106 changes: 106 additions & 0 deletions packages/wp-scripts-config/tests/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use strict';

const path = require( 'path' );
const sass = require( 'sass' );
const config = require( '../src/index.js' );
const disableSassCharset = require( '../src/transforms/disable-sass-charset' );

const BOM = '';

const sassLoaders = ( webpackConfig ) =>
( webpackConfig.module?.rules ?? [] )
.filter( ( rule ) => Array.isArray( rule.use ) )
.flatMap( ( rule ) => rule.use )
.filter( ( use ) => use?.loader?.includes?.( 'sass-loader' ) );

// webpack is a nested dependency of @wordpress/scripts, so resolve it from there.
const webpack = () => {
const scripts = path.dirname(
require.resolve( '@wordpress/scripts/package.json' )
);

return require( require.resolve( 'webpack', { paths: [ scripts ] } ) );
};

describe( 'the exported config', () => {
test( 'passes webpack’s own schema validation', () => {
// Attaching anything but config keys to the export makes every consuming
// build fail with "configuration has an unknown property".
expect( () => webpack().validate( config ) ).not.toThrow();
} );

test( 'still has a sass-loader rule', () => {
// A silent no-op would be worse than a failure: nothing would warn.
expect( sassLoaders( config ).length ).toBeGreaterThan( 0 );
} );

test( 'sets charset: false while keeping the existing options', () => {
for ( const loader of sassLoaders( config ) ) {
expect( loader.options.sassOptions.charset ).toBe( false );
expect( loader.options ).toHaveProperty( 'sourceMap' );
}
} );
} );

describe( 'disableSassCharset', () => {
const fixture = () => ( {
module: {
rules: [
{ test: /\.jsx?$/, use: [ { loader: 'babel-loader' } ] },
{ test: /\.svg$/, use: 'raw-loader' },
{},
{
test: /\.(sc|sa)ss$/,
use: [
{ loader: 'css-loader' },
{
loader: '/abs/node_modules/sass-loader/dist/cjs.js',
options: {
sourceMap: true,
sassOptions: { quietDeps: true },
},
},
],
},
],
},
} );

test( 'merges into existing sassOptions instead of replacing them', () => {
const result = disableSassCharset( fixture() );

expect( sassLoaders( result )[ 0 ].options.sassOptions ).toEqual( {
quietDeps: true,
charset: false,
} );
} );

test( 'leaves other loaders alone', () => {
const result = disableSassCharset( fixture() );

expect( result.module.rules[ 0 ].use[ 0 ] ).toEqual( {
loader: 'babel-loader',
} );
} );
} );

describe( 'the Sass behaviour this works around', () => {
const source = ':root{--brand:#037d96}.a::before{content:"“"}';

test( 'compressed output is BOM-prefixed by default', () => {
const { css } = sass.compileString( source, { style: 'compressed' } );

expect( css.startsWith( BOM ) ).toBe( true );
} );

test( 'the charset option the package sets drops the BOM', () => {
const { css } = sass.compileString( source, {
style: 'compressed',
...sassLoaders( config )[ 0 ].options.sassOptions,
} );

expect( css.startsWith( BOM ) ).toBe( false );
expect( css ).toContain( '“' );
expect( css ).toContain( '--brand:#037d96' );
} );
} );
10 changes: 10 additions & 0 deletions packages/wp-scripts-config/vitest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.test.js'],
testTimeout: 15000,
},
});
Loading