Skip to content

Commit 57f7314

Browse files
committed
minor #2326 [DX] Add per-package Yarn scripts (build, watch, test, lint, ...) (Kocal)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [DX] Add per-package Yarn scripts (build, watch, test, lint, ...) | Q | A | ------------- | --- | Bug fix? | no | New feature? | no <!-- please update src/**/CHANGELOG.md files --> | Issues | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead --> | License | MIT <!-- Replace this notice by a description of your feature/bugfix. This will help reviewers and should be a good start for the documentation. Additionally (see https://symfony.com/releases): - Always add tests and ensure they pass. - For new features, provide some code snippets to help understand usage. - Features and deprecations must be submitted against branch main. - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry - Never break backward compatibility (see https://symfony.com/bc). --> When working in UX Map, it was very painful to build or watch modifications on `.ts` files, I had investigate how the building process worked, and had to run Rollup like this, which is far from ideal (not documented and not obvious): ``` ./node_modules/.bin/rollup -c --environment INPUT_FILE:src/Map/assets/src/abstract_map_controller.ts -w ./node_modules/.bin/rollup -c --environment INPUT_FILE:src/Map/src/Bridge/Google/assets/src/map_controller.ts -w ./node_modules/.bin/rollup -c --environment INPUT_FILE:src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts -w ``` And I also felt sorry for `@rrr63` when he worked on adding Polygons on Map (#2162)... This PR improves the way developers will work on UX, and makes their lives easier. **Before**: - `yarn build` compiled the assets from ALL packages, it was not possible to build packages from only one package (which is useful if you work on a single package) - no `yarn watch` - `yarn test` runned tests from ALL packages, like `yarn build`, it was not possible to run tests for only one package **Now:** - at the project root, `build`/`test`/`lint`/`format`/`check-lint`/`check-format` scripts will run on all assets **from all packages**. And it will be faster than before, when processing was sequential, but now it's parallelized. - at a package root (ex: `src/Map/assets`), `build`/`test`/`lint`/`format`/`check-lint`/`check-format` scripts will run on all assets **from this package only** - `build` and `watch` scripts handles both TypeScript and CSS files in a single command This is a first step to what we spoke about with `@smnandre` to write a contribution guide. It is now much more easier and friendlier to tell a developer to run `yarn watch` inside a package root, instead of telling it to run `./node_modules/.bin/rollup -c --environment INPUT_FILE:src/Map/src/Bridge/Google/assets/src/map_controller.ts -w` (and more, if you have multiple files). <img width="1210" alt="image" src="https://github.com/user-attachments/assets/f40d4efc-439d-4f83-a0f8-611b1a64b334"> Commits ------- b8dc5fd Ignore `var` folder in Biome.js 8397b52 [DX] Rework testing process, add `test` script to packages package.json bc1d63c [DX] Reconfigure lint/format tasks to not run the command for each workspace, since Biome is super fast (also include bin and test JS files) 1873b3e [DX] Rework building process, and add build/watch scripts to packages package.json 5517388 [DX] Add lint/format/check-lint/check-format scripts to packages package.json
2 parents faaa385 + b8dc5fd commit 57f7314

File tree

30 files changed

+481
-338
lines changed

30 files changed

+481
-338
lines changed

bin/build_javascript.js

Lines changed: 0 additions & 43 deletions
This file was deleted.

bin/build_package.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* This file is used to compile the assets from an UX package.
3+
*/
4+
5+
const { parseArgs } = require('node:util');
6+
const path = require('node:path');
7+
const fs = require('node:fs');
8+
const glob = require('glob');
9+
const rollup = require('rollup');
10+
const CleanCSS = require('clean-css');
11+
const { getRollupConfiguration } = require('./rollup');
12+
13+
const args = parseArgs({
14+
allowPositionals: true,
15+
options: {
16+
watch: {
17+
type: 'boolean',
18+
description: 'Watch the source files for changes and rebuild when necessary.',
19+
},
20+
},
21+
});
22+
23+
async function main() {
24+
const packageRoot = path.resolve(process.cwd(), args.positionals[0]);
25+
26+
if (!fs.existsSync(packageRoot)) {
27+
console.error(`The package directory "${packageRoot}" does not exist.`);
28+
process.exit(1);
29+
}
30+
31+
if (!fs.existsSync(path.join(packageRoot, 'package.json'))) {
32+
console.error(`The package directory "${packageRoot}" does not contain a package.json file.`);
33+
process.exit(1);
34+
}
35+
36+
const packageData = require(path.join(packageRoot, 'package.json'));
37+
const packageName = packageData.name;
38+
const srcDir = path.join(packageRoot, 'src');
39+
const distDir = path.join(packageRoot, 'dist');
40+
41+
if (!fs.existsSync(srcDir)) {
42+
console.error(`The package directory "${packageRoot}" does not contain a "src" directory.`);
43+
process.exit(1);
44+
}
45+
46+
if (fs.existsSync(distDir)) {
47+
console.log(`Cleaning up the "${distDir}" directory...`);
48+
await fs.promises.rm(distDir, { recursive: true });
49+
await fs.promises.mkdir(distDir);
50+
}
51+
52+
const inputScriptFiles = [
53+
...glob.sync(path.join(srcDir, '*controller.ts')),
54+
...(['@symfony/ux-react', '@symfony/ux-vue', '@symfony/ux-svelte'].includes(packageName)
55+
? [path.join(srcDir, 'loader.ts'), path.join(srcDir, 'components.ts')]
56+
: []),
57+
...(packageName === '@symfony/stimulus-bundle'
58+
? [path.join(srcDir, 'loader.ts'), path.join(srcDir, 'controllers.ts')]
59+
: []),
60+
];
61+
62+
const inputStyleFile = packageData.config?.css_source;
63+
const buildCss = async () => {
64+
const inputStyleFileDist = inputStyleFile
65+
? path.resolve(distDir, `${path.basename(inputStyleFile, '.css')}.min.css`)
66+
: undefined;
67+
if (!inputStyleFile) {
68+
return;
69+
}
70+
71+
console.log('Minifying CSS...');
72+
const css = await fs.promises.readFile(inputStyleFile, 'utf-8');
73+
const minified = new CleanCSS().minify(css).styles;
74+
await fs.promises.writeFile(inputStyleFileDist, minified);
75+
};
76+
77+
if (inputScriptFiles.length === 0) {
78+
console.error(
79+
`No input files found for package "${packageName}" (directory "${packageRoot}").\nEnsure you have at least a file matching the pattern "src/*_controller.ts", or manually specify input files in "${__filename}" file.`
80+
);
81+
process.exit(1);
82+
}
83+
84+
const rollupConfig = getRollupConfiguration({ packageRoot, inputFiles: inputScriptFiles });
85+
86+
if (args.values.watch) {
87+
console.log(
88+
`Watching for JavaScript${inputStyleFile ? ' and CSS' : ''} files modifications in "${srcDir}" directory...`
89+
);
90+
91+
if (inputStyleFile) {
92+
rollupConfig.plugins = (rollupConfig.plugins || []).concat({
93+
name: 'watcher',
94+
buildStart() {
95+
this.addWatchFile(inputStyleFile);
96+
},
97+
});
98+
}
99+
100+
const watcher = rollup.watch(rollupConfig);
101+
watcher.on('event', ({ result }) => {
102+
if (result) {
103+
result.close();
104+
}
105+
});
106+
watcher.on('change', async (id, { event }) => {
107+
if (event === 'update') {
108+
console.log('Files were modified, rebuilding...');
109+
}
110+
111+
if (inputStyleFile && id === inputStyleFile) {
112+
await buildCss();
113+
}
114+
});
115+
} else {
116+
console.log(`Building JavaScript files from ${packageName} package...`);
117+
const start = Date.now();
118+
119+
const bundle = await rollup.rollup(rollupConfig);
120+
await bundle.write(rollupConfig.output);
121+
122+
await buildCss();
123+
124+
console.log(`Done in ${((Date.now() - start) / 1000).toFixed(3)} seconds.`);
125+
}
126+
}
127+
128+
main();

bin/build_styles.js

Lines changed: 0 additions & 46 deletions
This file was deleted.

bin/rollup.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
const fs = require('node:fs');
2+
const path = require('node:path');
3+
const resolve = require('@rollup/plugin-node-resolve');
4+
const commonjs = require('@rollup/plugin-commonjs');
5+
const typescript = require('@rollup/plugin-typescript');
6+
const glob = require('glob');
7+
8+
/**
9+
* Guarantees that any files imported from a peer dependency are treated as an external.
10+
*
11+
* For example, if we import `chart.js/auto`, that would not normally
12+
* match the "chart.js" we pass to the "externals" config. This plugin
13+
* catches that case and adds it as an external.
14+
*
15+
* Inspired by https://github.com/oat-sa/rollup-plugin-wildcard-external
16+
*/
17+
const wildcardExternalsPlugin = (peerDependencies) => ({
18+
name: 'wildcard-externals',
19+
resolveId(source, importer) {
20+
if (importer) {
21+
let matchesExternal = false;
22+
peerDependencies.forEach((peerDependency) => {
23+
if (source.includes(`/${peerDependency}/`)) {
24+
matchesExternal = true;
25+
}
26+
});
27+
28+
if (matchesExternal) {
29+
return {
30+
id: source,
31+
external: true,
32+
moduleSideEffects: true,
33+
};
34+
}
35+
}
36+
37+
return null; // other ids should be handled as usually
38+
},
39+
});
40+
41+
/**
42+
* Moves the generated TypeScript declaration files to the correct location.
43+
*
44+
* This could probably be configured in the TypeScript plugin.
45+
*/
46+
const moveTypescriptDeclarationsPlugin = (packageRoot) => ({
47+
name: 'move-ts-declarations',
48+
writeBundle: async () => {
49+
const isBridge = packageRoot.includes('src/Bridge');
50+
const globPattern = path.join('dist', '**', 'assets', 'src', '**/*.d.ts');
51+
const files = glob.sync(globPattern);
52+
53+
files.forEach((file) => {
54+
const relativePath = file;
55+
// a bit odd, but remove first 7 or 4 directories, which will leave only the relative path to the file
56+
// ex: dist/Chartjs/assets/src/controller.d.ts' => 'dist/controller.d.ts'
57+
const targetFile = relativePath.replace(
58+
`${relativePath
59+
.split('/')
60+
.slice(1, isBridge ? 7 : 4)
61+
.join('/')}/`,
62+
''
63+
);
64+
if (!fs.existsSync(path.dirname(targetFile))) {
65+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
66+
}
67+
fs.renameSync(file, targetFile);
68+
});
69+
},
70+
});
71+
72+
/**
73+
* @param {String} packageRoot
74+
* @param {Array<String>} inputFiles
75+
*/
76+
function getRollupConfiguration({ packageRoot, inputFiles }) {
77+
const packagePath = path.join(packageRoot, 'package.json');
78+
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
79+
const peerDependencies = [
80+
'@hotwired/stimulus',
81+
...(packageData.peerDependencies ? Object.keys(packageData.peerDependencies) : []),
82+
];
83+
84+
inputFiles.forEach((file) => {
85+
// custom handling for StimulusBundle
86+
if (file.includes('StimulusBundle/assets/src/loader.ts')) {
87+
peerDependencies.push('./controllers.js');
88+
}
89+
90+
// React, Vue
91+
if (file.includes('assets/src/loader.ts')) {
92+
peerDependencies.push('./components.js');
93+
}
94+
});
95+
96+
const outDir = path.join(packageRoot, 'dist');
97+
98+
return {
99+
input: inputFiles,
100+
output: {
101+
dir: outDir,
102+
entryFileNames: '[name].js',
103+
format: 'esm',
104+
},
105+
external: peerDependencies,
106+
plugins: [
107+
resolve(),
108+
typescript({
109+
filterRoot: '.',
110+
tsconfig: path.join(__dirname, '..', 'tsconfig.json'),
111+
include: [
112+
'src/**/*.ts',
113+
// TODO: Remove for the next major release
114+
// "@rollup/plugin-typescript" v11.0.0 fixed an issue (https://github.com/rollup/plugins/pull/1310) that
115+
// cause a breaking change for UX React users, the dist file requires "react-dom/client" instead of "react-dom"
116+
// and it will break for users using the Symfony AssetMapper without Symfony Flex (for automatic "importmap.php" upgrade).
117+
'**/node_modules/react-dom/client.js',
118+
],
119+
compilerOptions: {
120+
outDir: outDir,
121+
declaration: true,
122+
emitDeclarationOnly: true,
123+
},
124+
}),
125+
commonjs(),
126+
wildcardExternalsPlugin(peerDependencies),
127+
moveTypescriptDeclarationsPlugin(packageRoot),
128+
],
129+
};
130+
}
131+
132+
module.exports = {
133+
getRollupConfiguration,
134+
};

0 commit comments

Comments
 (0)