diff --git a/package-lock.json b/package-lock.json index f6d786b..313693c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "postcss-loader": "4.3.0", "postcss-simple-vars": "^5.0.1", "style-loader": "4.0.0", + "ts-loader": "^9.5.1", "url-loader": "4.1.1", "webpack": "^5.90.3" } @@ -5047,7 +5048,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -6637,7 +6637,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -7735,7 +7734,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -10452,7 +10450,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -13686,7 +13683,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -19106,7 +19102,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -19145,6 +19140,126 @@ "node": ">=8" } }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", @@ -19179,7 +19294,6 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, "license": "Apache-2.0", "peer": true, "bin": { diff --git a/package.json b/package.json index e7e7507..596329b 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "postcss-loader": "4.3.0", "postcss-simple-vars": "^5.0.1", "style-loader": "4.0.0", + "ts-loader": "^9.5.1", "url-loader": "4.1.1", "webpack": "^5.90.3" } diff --git a/src/index.cjs b/src/index.cjs index ac41ac6..c241877 100644 --- a/src/index.cjs +++ b/src/index.cjs @@ -3,6 +3,7 @@ const path = require('path'); const merge = require('lodash.merge'); const nodeExternals = require('webpack-node-externals'); const webpack = require('webpack'); +const TerserPlugin = require("terser-webpack-plugin") const DEFAULT_CHUNK_FILENAME = 'chunks/[name].[chunkhash].js'; const DEFAULT_ASSET_FILENAME = 'assets/[name].[hash][ext][query]'; @@ -30,14 +31,15 @@ const toPath = path => { class ScratchWebpackConfigBuilder { /** * @param {object} options Options for the webpack configuration. - * @param {string|URL} options.rootPath The absolute path to the project root. + * @param {string|URL} [options.rootPath] The absolute path to the project root. * @param {string|URL} [options.distPath] The absolute path to build output. Defaults to `dist` under `rootPath`. + * @param {string|URL} [options.publicPath] The public location where the output assets will be located. Defaults to `/`. * @param {boolean} [options.enableReact] Whether to enable React and JSX support. * @param {string} [options.libraryName] The name of the library to build. Shorthand for `output.library.name`. * @param {string|URL} [options.srcPath] The absolute path to the source files. Defaults to `src` under `rootPath`. * @param {boolean} [options.shouldSplitChunks] Whether to enable spliting code to chunks. */ - constructor ({ distPath, enableReact, libraryName, rootPath, srcPath, shouldSplitChunks }) { + constructor ({ distPath, enableReact, enableTs, libraryName, rootPath, srcPath, publicPath = '/', shouldSplitChunks }) { const isProduction = process.env.NODE_ENV === 'production'; const mode = isProduction ? 'production' : 'development'; @@ -58,6 +60,14 @@ class ScratchWebpackConfigBuilder { } : path.resolve(this._srcPath, 'index'), optimization: { minimize: isProduction, + minimizer: [ + new TerserPlugin({ + // Limiting Terser to use only 2 threads. At least for building scratch-gui + // this results in a performance gain (from ~60s to ~36s) on a MacBook with + // M1 Pro and 32GB of RAM and halving the memory usage (from ~11GB at peaks to ~6GB) + parallel: 2 + }) + ], ...( shouldSplitChunks ? { splitChunks: { @@ -74,6 +84,8 @@ class ScratchWebpackConfigBuilder { assetModuleFilename: DEFAULT_ASSET_FILENAME, chunkFilename: DEFAULT_CHUNK_FILENAME, path: this._distPath, + // See https://github.com/scratchfoundation/scratch-editor/pull/25/files/9bc537f9bce35ee327b74bd6715d6c5140f73937#r1763073684 + publicPath, library: { name: libraryName, type: 'umd2' @@ -90,6 +102,7 @@ class ScratchWebpackConfigBuilder { '.jsx' ] : [] ), + ...(enableTs ? ['.ts', '.tsx'] : []), // webpack supports '...' to include defaults, but eslint does not '.js', '.json' @@ -98,12 +111,18 @@ class ScratchWebpackConfigBuilder { module: { rules: [ { - test: enableReact ? /\.[cm]?jsx?$/ : /\.[cm]?js$/, + test: enableReact ? + (enableTs ? /\.[cm]?[jt]sx?$/ : /\.[cm]?jsx?$/) : + (enableTs ? /\.[cm]?[jt]s$/ : /\.[cm]?js$/), loader: 'babel-loader', exclude: [ { and: [/node_modules/], - not: [/node_modules[\\/].*scratch/] + + // Some scratch pakcages point to their source (as opposed to a pre-built version) + // for their browser or webpack target. So we need to process them (at the minimum + // to resolve the JSX syntax). + not: [/node_modules[\\/]scratch-(paint|render|svg-renderer|vm)[\\/]src[\\/]/] } ], options: { @@ -196,9 +215,13 @@ class ScratchWebpackConfigBuilder { ] } ] : [] - ) + ), + ...(enableTs ? [{ + test: enableReact ? /\.[cm]?tsx?$/ : /\.[cm]?ts$/, + loader: 'ts-loader', + exclude: [/node_modules/] + }] : []), ], - }, plugins: [ new webpack.ProvidePlugin({ @@ -238,6 +261,16 @@ class ScratchWebpackConfigBuilder { return this; } + /** + * Append new externals to the current configuration object. + * @param {string[]} externals Externals to add. + * @returns {this} + */ + addExternals(externals) { + this._config.externals = (this._config.externals ?? []).concat(externals); + return this; + } + /** * Set the target environment for this configuration. * @param {string} target The target environment, like `node`, `browserslist`, etc.