From d6902e23493e740f5222192b8350d34d8d061025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=BCnter?= Date: Fri, 22 May 2020 09:07:52 +0200 Subject: [PATCH] build: improve plugin build with webpack parallel builds --- .gitlab-ci.ts | 2 + common/Gruntfile.plugin.ts | 18 +------ common/webpack-loader-noop.js | 7 +++ common/webpack.factory.ts | 64 ++++++++++++++++++++----- common/webpack.multi.ts | 36 ++++++++++++-- plugins/wp-reactjs-starter/package.json | 2 +- 6 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 common/webpack-loader-noop.js diff --git a/.gitlab-ci.ts b/.gitlab-ci.ts index 381f127..003fb84 100644 --- a/.gitlab-ci.ts +++ b/.gitlab-ci.ts @@ -61,10 +61,12 @@ const createConfig: CreateConfigFunction = async () => { "export TMP_FILES=$(cd $TMP_CI_PROJECT_DIR && eval find $INSTALL_VENDOR_FOLDERS -maxdepth 0 2>/dev/null)", "echo $TMP_FILES", "time for dirs in $TMP_FILES; do ln -s $TMP_CI_PROJECT_DIR/$dirs $dirs; done", + "git stash", // Make sure all dependencies are installed correctly "yarn bootstrap", // Make sure cypress is installed correctly "test $DOCKER_DAEMON_ALLOW_UP && yarn cypress install", + "git stash pop || :", // Recreate our local package symlinks `for sym in $(find node_modules/@$COMPOSE_PROJECT_NAME/ {plugins,packages}/*/node_modules/@$COMPOSE_PROJECT_NAME {plugins,packages}/*/vendor/$COMPOSE_PROJECT_NAME -maxdepth 1 -type l 2>/dev/null); do ln -sf "$(realpath $sym | cut -c5-)" "$(dirname $sym)"; done` ] diff --git a/common/Gruntfile.plugin.ts b/common/Gruntfile.plugin.ts index fce264a..904a59f 100755 --- a/common/Gruntfile.plugin.ts +++ b/common/Gruntfile.plugin.ts @@ -182,22 +182,6 @@ function applyPluginRunnerConfiguration(grunt: IGrunt) { }); }); - /** - * Build a composer package before it will be installed in a build process. - */ - grunt.registerTask("composer:dependents:build", () => { - Object.keys(grunt.config.get<{ [key: string]: string }>("pkg.dependencies")) - .filter((dep) => dep.indexOf(`@${mainPkg.name}/`) > -1) - .map((dep) => dep.split("/")[1]) - .forEach((dep) => { - const cwd = resolve(process.cwd(), `../../packages/${dep}`); - if (!grunt.file.exists(resolve(cwd, "dist")) || !grunt.file.exists(resolve(cwd, "dev"))) { - grunt.log.writeln(`Building production package of ${dep} in ${cwd}...`); - execSync(`cd '${cwd}' && yarn build`, { stdio: "inherit" }); // we can not use `cwd` as option because yarn can not resolve packages then - } - }); - }); - /** * Composer does not allow to define to pack only a set of directories and files as yarn * allows with package.json#files (https://stackoverflow.com/a/17069547/5506547). For this, a @@ -351,10 +335,10 @@ function applyPluginRunnerConfiguration(grunt: IGrunt) { "clean:productionLibs", "strip_code:sourcemaps", "build:readme", - "composer:dependents:build", "composer:install:production", "composer:clean:production", "clean:productionSource", + "clean:webpackDevBundles", "strip_code:productionSource", "php:scope", "clean:packageManageFiles" diff --git a/common/webpack-loader-noop.js b/common/webpack-loader-noop.js new file mode 100644 index 0000000..02c95f6 --- /dev/null +++ b/common/webpack-loader-noop.js @@ -0,0 +1,7 @@ +/** + * This is useful to loaders which need to be still "indexed" in the `rules` array. + */ +module.exports = function (content) { + this.cacheable && this.cacheable(true); + return content; +}; diff --git a/common/webpack.factory.ts b/common/webpack.factory.ts index faa2a72..706286f 100755 --- a/common/webpack.factory.ts +++ b/common/webpack.factory.ts @@ -8,7 +8,7 @@ import { resolve, join } from "path"; import fs from "fs"; -import { Configuration, DefinePlugin, Compiler, Options } from "webpack"; +import { Configuration, DefinePlugin, Compiler, Options, ProvidePlugin } from "webpack"; import { spawn, execSync } from "child_process"; import WebpackBar from "webpackbar"; import MiniCssExtractPlugin from "mini-css-extract-plugin"; @@ -185,12 +185,14 @@ function createDefaultSettings( ) { const pwd = process.env.DOCKER_START_PWD || process.env.PWD; const NODE_ENV = (process.env.NODE_ENV as Configuration["mode"]) || "development"; + const CI = !!process.env.CI; const nodeEnvFolder = NODE_ENV === "production" ? "dist" : "dev"; const rootPkg = require(resolve(pwd, "../../package.json")); const pkg = require(resolve(pwd, "package.json")); const settings: Configuration[] = []; const plugins = getPlugins(pwd); const slug = type === "plugin" ? pkg.slug : pkg.name.split("/")[1]; + const NoopLoader = resolve(pwd, "../../common/webpack-loader-noop.js"); const rootSlugCamelCased = slugCamelCase(rootPkg.name); const packages = getPackages(pwd, rootSlugCamelCased); @@ -296,13 +298,15 @@ function createDefaultSettings( test: /\.tsx$/, exclude: /(disposables)/, use: [ - { - loader: "cache-loader", - options: { - cacheIdentifier: `cache-loader:${CacheLoaderVersion} ${NODE_ENV}${babelCacheIdentifier}` - } - }, - "thread-loader", + CI + ? NoopLoader + : { + loader: "cache-loader", + options: { + cacheIdentifier: `cache-loader:${CacheLoaderVersion} ${NODE_ENV}${babelCacheIdentifier}` + } + }, + CI ? NoopLoader : "thread-loader", { loader: "babel-loader?cacheDirectory", options: babelOptions @@ -349,9 +353,8 @@ function createDefaultSettings( }), new MiniCssExtractPlugin({ filename: "[name].css" - }), - new WebpackPluginDone(pwd) - ] + }) + ].concat(process.env.BUILD_PLUGIN ? [] : [new WebpackPluginDone(pwd)]) }; skipExternals.forEach((key) => { @@ -388,4 +391,41 @@ function createDefaultSettings( return settings; } -export { WebpackPluginDone, createDefaultSettings, slugCamelCase, getPlugins }; +/** + * In some cases it is more than recommend to use a lightweight alternative + * to React in your frontend. E. g. for plugin developers creating frontend + * solutions to non-logged-in users to reduce load time. + * + * Note: You need to apply `skipExternals: ["react", "react-dom"]`, too! + */ +function applyPreact(config: Configuration, disableChunks = false) { + // Disable splitChunks so no `vendor~banner.js` is created + if (disableChunks) { + delete config.optimization.splitChunks; + } + + // Implement preact and JSX transform through babel and webpack + // https://preactjs.com/guide/v10/getting-started#aliasing-in-webpack + config.resolve.alias = { + react: "preact/compat", + "react-dom": "preact/compat" + }; + + // https://preactjs.com/guide/v8/switching-to-preact/#2-jsx-pragma-transpile-to-h + // https://babeljs.io/docs/en/babel-preset-react#pragma + (config.module.rules[0].use as any[])[2].options.presets[2] = [ + "@babel/preset-react", + { + pragma: "h" + } + ]; + + // https://github.com/preactjs/preact-compat/issues/161#issuecomment-590806041 + config.plugins.push( + new ProvidePlugin({ + h: ["preact", "h"] + }) + ); +} + +export { WebpackPluginDone, createDefaultSettings, slugCamelCase, getPlugins, applyPreact }; diff --git a/common/webpack.multi.ts b/common/webpack.multi.ts index 8c3352a..1ca3b69 100644 --- a/common/webpack.multi.ts +++ b/common/webpack.multi.ts @@ -6,23 +6,51 @@ */ import glob from "glob"; -import { resolve, dirname } from "path"; +import { resolve, dirname, join } from "path"; + +const cwd = process.cwd(); +const rootCwd = resolve(join(__dirname, "..")); +const rootName = require(join(rootCwd, "package.json")).name; + +// Check if a single plugin should be built so we consider dependencies, too +const buildPlugin = process.env.BUILD_PLUGIN; +const buildPluginPwds = buildPlugin + ? Object.keys(require(join(cwd, "package.json")).dependencies) + .filter((dep) => dep.startsWith(`@${rootName}/`)) + .map((dep) => join(rootCwd, "packages", dep.split("/")[1])) + .concat([cwd]) + : undefined; + +if (buildPlugin) { + console.log( + "You are currently building a plugin, please consider to put your webpack:done actions after the `yarn build` command for performance reasons!" + ); +} export default async () => { const result = []; const configs = glob .sync("{plugins,packages}/*/scripts/webpack.config.ts", { - absolute: true + absolute: true, + cwd: rootCwd }) .map((path) => ({ pwd: resolve(dirname(path), "../"), path - })); + })) + .filter(({ pwd }) => { + // When we need to build a plugin, only consider dependent packages and own plugin + if (buildPlugin) { + return buildPluginPwds.indexOf(pwd) > -1; + } + return true; + }); for (const config of configs) { const { pwd, path } = config; process.env.DOCKER_START_PWD = pwd; - result.push(((await import(path)) as any).default); + process.env.NODE_ENV = buildPlugin ? "production" : "development"; + result.push(require(path).default); } return result.flat(); diff --git a/plugins/wp-reactjs-starter/package.json b/plugins/wp-reactjs-starter/package.json index 7ce06d2..e8d70ae 100644 --- a/plugins/wp-reactjs-starter/package.json +++ b/plugins/wp-reactjs-starter/package.json @@ -24,7 +24,7 @@ "dev": "yarn grunt libs:copy && concurrently --raw \"test $IS_DOCKER_START_COMMAND && exit 0 || yarn webpack --watch\" \"yarn --silent chokidar 'src/inc/**/*.php' -i 'src/inc/base/others/cachebuster*' -c 'yarn i18n:generate:backend' --silent\"", "i18n:generate:backend": "yarn --silent wp:weak i18n make-pot src/ src/languages/$(basename \"$(pwd)\").pot --ignore-domain --include=inc/ --exclude=public/", "i18n:generate:frontend": "test -d src/public/dev && yarn --silent grunt i18n:prepare:wp && yarn --silent wp:weak i18n make-pot src/public/dev/i18n-dir src/public/languages/$(basename \"$(pwd)\").pot --ignore-domain && rm -rf src/public/languages/json && yarn --silent wp i18n make-json src/public/languages src/public/languages/json --no-purge", - "build": "yarn --silent build:js:production && yarn --silent build:js:development && yarn grunt build", + "build": "BUILD_PLUGIN=$npm_package_slug yarn --silent parallel-webpack --no-stats --config ../../common/webpack.multi.ts && yarn grunt cachebuster:public && yarn grunt build", "build:js:production": "NODE_ENV=production yarn webpack", "build:js:development": "yarn webpack", "build:webpack:done": "yarn --silent concurrently -n cachebuster:public,i18n:generate:frontend 'yarn --silent grunt cachebuster:public' 'yarn --silent i18n:generate:frontend'",