diff --git a/.gitignore b/.gitignore index 7ab24b8..5c4368c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ logs/ # Directories node_modules/ lib/ +public/ # Configs .babelrc diff --git a/package.json b/package.json index cf9f50b..02302d5 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "setup": "tsc --build && beemo create-config eslint prettier", "build": "beemo typescript --build --reference-workspaces", "clean": "rm -rf packages/*/{lib,*.tsbuildinfo}", - "test": "yarn run build && yarn run jest && yarn run lint", + "test": "yarn run build && yarn run jest && yarn run lint && yarn run test:webpack", + "test:webpack": "NODE_ENV=production webpack --config ./packages/config-webpack/tests/webpack.test.config.js", "lint": "beemo eslint .", "jest": "beemo jest", "prettier": "beemo prettier", @@ -29,7 +30,8 @@ "eslint", "jest", "prettier", - "typescript" + "typescript", + "webpack" ], "settings": { "node": true diff --git a/packages/config-webpack/package.json b/packages/config-webpack/package.json index 2fa54ba..9ddec0c 100644 --- a/packages/config-webpack/package.json +++ b/packages/config-webpack/package.json @@ -33,7 +33,6 @@ "fast-glob": "^3.2.2", "file-loader": "^6.0.0", "html-webpack-plugin": "^4.0.1", - "inline-manifest-webpack-plugin": "^4.0.2", "is-docker": "^2.0.0", "terser-webpack-plugin": "^2.3.5", "url-loader": "^4.0.0", diff --git a/packages/config-webpack/src/index.ts b/packages/config-webpack/src/index.ts index 057f3d9..5ba6afc 100644 --- a/packages/config-webpack/src/index.ts +++ b/packages/config-webpack/src/index.ts @@ -3,7 +3,6 @@ import path from 'path'; import webpack from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; -import InlineManifestWebpackPlugin from 'inline-manifest-webpack-plugin'; import TerserPlugin from 'terser-webpack-plugin'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { WebpackConfig } from '@beemo/driver-webpack'; @@ -14,6 +13,7 @@ import { GQL_EXT_PATTERN, TJSX_EXT_PATTERN, } from '@airbnb/nimbus-common'; +import InlineManifestPlugin from './plugins/InlineManifestPlugin'; import { PORT, ROOT, PROD, getESMAliases, getFavIcon, getParallelValue } from './helpers'; export interface WebpackOptions { @@ -22,6 +22,7 @@ export interface WebpackOptions { parallel?: boolean | string | number; port?: string | number; react?: boolean; + root?: string; sourceMaps?: boolean; srcFolder: string; } @@ -32,11 +33,12 @@ export function getConfig({ parallel = true, port = PORT, react = false, + root = ROOT, sourceMaps = false, srcFolder, }: WebpackOptions): WebpackConfig { - const srcPath = path.join(ROOT, srcFolder); - const publicPath = path.join(ROOT, buildFolder); + const srcPath = path.join(root, srcFolder); + const publicPath = path.join(root, buildFolder); const entry = [srcPath]; const plugins = [ new webpack.NamedChunksPlugin(), @@ -65,7 +67,7 @@ export function getConfig({ if (PROD) { plugins.push( // Inline the runtime chunk to enable long-term caching - new InlineManifestWebpackPlugin(), + new InlineManifestPlugin(), ); } else if (react) { plugins.push( @@ -79,6 +81,8 @@ export function getConfig({ bail: PROD, + context: root, + entry: { core: entry, }, diff --git a/packages/config-webpack/src/plugins/InlineManifestPlugin.ts b/packages/config-webpack/src/plugins/InlineManifestPlugin.ts new file mode 100644 index 0000000..074d31c --- /dev/null +++ b/packages/config-webpack/src/plugins/InlineManifestPlugin.ts @@ -0,0 +1,97 @@ +/* eslint-disable no-param-reassign, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, import/no-extraneous-dependencies */ + +import webpack from 'webpack'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import sourceMappingURL from 'source-map-url'; + +function getAssetName(chunks: webpack.compilation.Chunk[], chunkName: string) { + return chunks.filter((chunk) => chunk.name === chunkName)?.[0]?.files[0]; +} + +function inlineWhenMatched( + compilation: webpack.compilation.Compilation, + scripts: HtmlWebpackPlugin.HtmlTagObject[], + manifestAssetName: string, +) { + return scripts.map((script) => { + const isManifestScript = + script.tagName === 'script' && + typeof script.attributes.src === 'string' && + script.attributes.src?.includes(manifestAssetName); + + if (isManifestScript) { + return { + tagName: 'script', + voidTag: true, + attributes: { + type: 'text/javascript', + }, + innerHTML: sourceMappingURL.removeFrom(compilation.assets[manifestAssetName].source()), + }; + } + + return script; + }); +} + +export default class InlineManifestPlugin implements webpack.Plugin { + name: string; + + constructor(name: string = 'runtime') { + this.name = name; + } + + apply(compiler: webpack.Compiler) { + const { name } = this; + + compiler.hooks.emit.tap('InlineManifestWebpackPlugin', (compilation) => { + const assetName = getAssetName(compilation.chunks, name); + + if (assetName) { + delete compilation.assets[assetName]; + } + }); + + compiler.hooks.compilation.tap('InlineManifestWebpackPlugin', (compilation) => { + const hooks = HtmlWebpackPlugin.getHooks(compilation); + + hooks.alterAssetTags.tapAsync('InlineManifestWebpackPlugin', (data, cb) => { + const assetName = getAssetName(compilation.chunks, name); + + if (assetName) { + data.assetTags.scripts = inlineWhenMatched( + compilation, + data.assetTags.scripts, + assetName, + ); + } + + cb(null, data); + }); + + hooks.beforeAssetTagGeneration.tapAsync( + 'InlineManifestWebpackPlugin', + (htmlPluginData, cb) => { + const assetName = getAssetName(compilation.chunks, name); + + // @ts-ignore Option exists + if (assetName && htmlPluginData.plugin.options.inject === false) { + const { assets } = htmlPluginData; + const runtime = ``; + const runtimeIndex = assets.js.indexOf(assets.publicPath + assetName); + + if (runtimeIndex >= 0) { + assets.js.splice(runtimeIndex, 1); + } + + assets.js.push(runtime); + } + + cb(null, htmlPluginData); + }, + ); + }); + } +} diff --git a/packages/config-webpack/tests/src/index.html b/packages/config-webpack/tests/src/index.html new file mode 100644 index 0000000..d40c7db --- /dev/null +++ b/packages/config-webpack/tests/src/index.html @@ -0,0 +1,11 @@ + + + + Test + + + + +
+ + diff --git a/packages/config-webpack/tests/src/index.js b/packages/config-webpack/tests/src/index.js new file mode 100644 index 0000000..aa94599 --- /dev/null +++ b/packages/config-webpack/tests/src/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-console +console.log('Webpack build test!'); diff --git a/packages/config-webpack/tests/webpack.test.config.js b/packages/config-webpack/tests/webpack.test.config.js new file mode 100644 index 0000000..729cec0 --- /dev/null +++ b/packages/config-webpack/tests/webpack.test.config.js @@ -0,0 +1,6 @@ +const { getConfig } = require('../lib'); + +module.exports = getConfig({ + root: __dirname, + srcFolder: 'src', +}); diff --git a/types/inline-manifest-webpack-plugin.d.ts b/types/inline-manifest-webpack-plugin.d.ts deleted file mode 100644 index 7fbe1eb..0000000 --- a/types/inline-manifest-webpack-plugin.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module 'inline-manifest-webpack-plugin' { - export default class InlineManifestWebpackPlugin { - apply(): this; - } -} diff --git a/types/source-map-url.d.ts b/types/source-map-url.d.ts new file mode 100644 index 0000000..3d4d88a --- /dev/null +++ b/types/source-map-url.d.ts @@ -0,0 +1,8 @@ +declare module 'source-map-url' { + class SMU { + removeFrom(code: string): string; + } + + const smu: SMU; + export default smu; +} diff --git a/yarn.lock b/yarn.lock index 34e0b48..5767873 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6797,13 +6797,6 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" -inline-manifest-webpack-plugin@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/inline-manifest-webpack-plugin/-/inline-manifest-webpack-plugin-4.0.2.tgz#39bf4144c3a6210686bfe44c73f2c883681e51bb" - integrity sha512-j1Q0Y7m2GVsTxnOzQ7YzIlfn5Th2Ga6Ivoqme1G0iGZc8m7R3aQY8HfzLW7ew3CwmqdZb/O26mf9Ak2JA7zzKg== - dependencies: - source-map-url "0.4.0" - inquirer@^6.2.0: version "6.5.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.1.tgz#8bfb7a5ac02dac6ff641ac4c5ff17da112fcdb42" @@ -10940,7 +10933,7 @@ source-map-support@^0.5.0, source-map-support@^0.5.6, source-map-support@~0.5.12 buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@0.4.0, source-map-url@^0.4.0: +source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=