From 0747fd41b94603900c8759511ca18f7c9e2e03ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Wed, 8 Jul 2020 15:01:13 +0200 Subject: [PATCH] feat: refactor plugins - PreloadBundlesPlugin, PreloadModulesDllPlugin and LooseModePlugin (#749) --- .../scripts/VSComponentList.ps1 | 2 +- packages/haul-core/package.json | 2 +- packages/haul-core/src/config/types.ts | 11 +- packages/haul-core/src/index.ts | 12 +- .../makeConfigFactory.test.ts.snap | 112 +++--- .../__tests__/makeConfigFactory.test.ts | 82 +---- .../haul-core/src/preset/makeConfigFactory.ts | 49 +-- packages/haul-core/src/types.ts | 7 + .../plugins/BasicBundleWebpackPlugin.ts | 29 -- .../src/webpack/plugins/InitCoreDllPlugin.ts | 147 -------- .../src/webpack/plugins/LooseModePlugin.ts | 76 ++++ .../webpack/plugins/LooseModeWebpackPlugin.ts | 35 -- .../webpack/plugins/PreloadBundlesPlugin.ts | 34 ++ .../plugins/PreloadModulesDllPlugin.ts | 162 +++++++++ .../FileRamBundle.ts | 18 +- .../IndexRamBundle.ts | 4 +- .../RamBundlePlugin/RamBundlePlugin.ts | 308 +++++++++++++++++ .../__tests__/FileRamBundle.test.ts | 10 +- .../__tests__/IndexRamBundle.test.ts | 2 +- .../__tests__/__fixtures__/crashFn.async.js | 0 .../__tests__/__fixtures__/hello.cjs.js | 0 .../__tests__/__fixtures__/hello.esm.js | 0 .../__tests__/__fixtures__/index.js | 0 .../__fixtures__/nestedCrashFn.esm.js | 0 .../__tests__/__fixtures__/transpiled.js | 0 .../__tests__/integration.test.ts | 24 +- .../webpack/plugins/RamBundlePlugin/index.ts | 1 + .../minifyWorker.ts} | 0 .../RamBundleWebpackPlugin.ts | 324 ------------------ .../plugins/RamBundleWebpackPlugin/index.ts | 1 - .../plugins/RamBundleWebpackPlugin/utils.ts | 2 - .../plugins/__tests__/LooseModePlugin.test.ts | 71 ++++ .../__tests__/LooseModeWebpackPlugin.test.ts | 39 --- ...n.test.ts => PreloadBundlesPlugin.test.ts} | 6 +- .../plugins/__tests__/__fixtures__/async.js | 2 + .../plugins/__tests__/__fixtures__/entry.js | 2 + .../plugins/__tests__/__fixtures__/index.js | 2 + 37 files changed, 793 insertions(+), 783 deletions(-) create mode 100644 packages/haul-core/src/types.ts delete mode 100644 packages/haul-core/src/webpack/plugins/BasicBundleWebpackPlugin.ts delete mode 100644 packages/haul-core/src/webpack/plugins/InitCoreDllPlugin.ts create mode 100644 packages/haul-core/src/webpack/plugins/LooseModePlugin.ts delete mode 100644 packages/haul-core/src/webpack/plugins/LooseModeWebpackPlugin.ts create mode 100644 packages/haul-core/src/webpack/plugins/PreloadBundlesPlugin.ts create mode 100644 packages/haul-core/src/webpack/plugins/PreloadModulesDllPlugin.ts rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/FileRamBundle.ts (83%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/IndexRamBundle.ts (97%) create mode 100644 packages/haul-core/src/webpack/plugins/RamBundlePlugin/RamBundlePlugin.ts rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/FileRamBundle.test.ts (88%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/IndexRamBundle.test.ts (95%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/__fixtures__/crashFn.async.js (100%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/__fixtures__/hello.cjs.js (100%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/__fixtures__/hello.esm.js (100%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/__fixtures__/index.js (100%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/__fixtures__/nestedCrashFn.esm.js (100%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/__fixtures__/transpiled.js (100%) rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin => RamBundlePlugin}/__tests__/integration.test.ts (95%) create mode 100644 packages/haul-core/src/webpack/plugins/RamBundlePlugin/index.ts rename packages/haul-core/src/webpack/plugins/{RamBundleWebpackPlugin/worker.ts => RamBundlePlugin/minifyWorker.ts} (100%) delete mode 100644 packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/RamBundleWebpackPlugin.ts delete mode 100644 packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/index.ts delete mode 100644 packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/utils.ts create mode 100644 packages/haul-core/src/webpack/plugins/__tests__/LooseModePlugin.test.ts delete mode 100644 packages/haul-core/src/webpack/plugins/__tests__/LooseModeWebpackPlugin.test.ts rename packages/haul-core/src/webpack/plugins/__tests__/{WebpackBasicBundlePlugin.test.ts => PreloadBundlesPlugin.test.ts} (90%) diff --git a/fixtures/react_native_windows_e2e_app/scripts/VSComponentList.ps1 b/fixtures/react_native_windows_e2e_app/scripts/VSComponentList.ps1 index e1f42e56..68d96a67 100644 --- a/fixtures/react_native_windows_e2e_app/scripts/VSComponentList.ps1 +++ b/fixtures/react_native_windows_e2e_app/scripts/VSComponentList.ps1 @@ -22,4 +22,4 @@ do Get-Content $dir\err Get-Content $dir\out -Get-Content $vsconfig \ No newline at end of file +Get-Content $vsconfig diff --git a/packages/haul-core/package.json b/packages/haul-core/package.json index f47d9464..04f0c16b 100644 --- a/packages/haul-core/package.json +++ b/packages/haul-core/package.json @@ -1,7 +1,7 @@ { "version": "0.22.1", "name": "@haul-bundler/core", - "description": "Haul core library (legacy)", + "description": "Haul core library", "main": "build/index.js", "files": [ "build/", diff --git a/packages/haul-core/src/config/types.ts b/packages/haul-core/src/config/types.ts index 0a52d57b..10f7b0fc 100644 --- a/packages/haul-core/src/config/types.ts +++ b/packages/haul-core/src/config/types.ts @@ -2,6 +2,7 @@ import webpack from 'webpack'; import { MinifyOptions } from 'terser'; import { DeepNonNullable, Overwrite, Assign } from 'utility-types'; import Runtime from '../runtime/Runtime'; +import { LooseModeConfig, BundleType, BundlingMode } from '../types'; export type ServerConfig = { port?: number; @@ -15,8 +16,8 @@ export type EnvOptions = { platform: string; root: string; dev: boolean; - bundleType?: 'basic-bundle' | 'indexed-ram-bundle' | 'file-ram-bundle'; - bundleMode: 'single-bundle' | 'multi-bundle'; + bundleType?: BundleType; + bundleMode: BundlingMode; bundleTarget?: 'file' | 'server'; assetsDest?: string; bundleOutput?: string; @@ -30,7 +31,7 @@ export type BundleConfig = Assign< { name?: string; entry: string | string[] | { entryFiles: string[]; setupFiles: string[] }; - type?: 'basic-bundle' | 'indexed-ram-bundle' | 'file-ram-bundle'; + type?: BundleType; platform?: string; root?: string; dev?: boolean; @@ -41,7 +42,7 @@ export type BundleConfig = Assign< Exclude >; sourceMap?: boolean | 'inline'; - looseMode?: true | Array | ((filename: string) => boolean); + looseMode?: LooseModeConfig; dll?: boolean; app?: boolean; dependsOn?: string[]; @@ -95,7 +96,7 @@ export type NormalizedBundleConfig = Assign< DeepNonNullable, { manifestPath: ExternalBundleConfig['manifestPath'] } >; - looseMode: (filename: string) => boolean; + looseMode?: LooseModeConfig; } >; diff --git a/packages/haul-core/src/index.ts b/packages/haul-core/src/index.ts index 334c1257..a94578d0 100644 --- a/packages/haul-core/src/index.ts +++ b/packages/haul-core/src/index.ts @@ -6,10 +6,14 @@ export { default as getBabelConfigPath } from './preset/getBabelConfigPath'; export { default as makeConfigFactory } from './preset/makeConfigFactory'; export { getBundleFilename } from './preset/utils/applyMultiBundleTweaks'; -// Webpack utils +// Webpack export { default as AssetResolver } from './webpack/resolvers/AssetResolver'; export { default as HasteResolver } from './webpack/resolvers/HasteResolver'; export { default as resolveModule } from './webpack/resolvers/resolveModule'; +export { LooseModePlugin } from './webpack/plugins/LooseModePlugin'; +export { PreloadBundlesPlugin } from './webpack/plugins/PreloadBundlesPlugin'; +export { PreloadModulesDllPlugin } from './webpack/plugins/PreloadModulesDllPlugin'; +export { RamBundlePlugin } from './webpack/plugins/RamBundlePlugin'; // Shared CLI utils export { default as Runtime } from './runtime/Runtime'; @@ -36,6 +40,12 @@ export { NormalizedProjectConfig, NormalizedProjectConfigBuilder, } from './config/types'; +export { + BundleType, + BundlingMode, + RamBundleType, + LooseModeConfig, +} from './types'; export { default as getReactNativeVersion } from './utils/getReactNativeVersion'; export { default as parseEntry } from './utils/parseEntry'; export { default as sortBundlesByDependencies } from './utils/sortBundlesByDependencies'; diff --git a/packages/haul-core/src/preset/__tests__/__snapshots__/makeConfigFactory.test.ts.snap b/packages/haul-core/src/preset/__tests__/__snapshots__/makeConfigFactory.test.ts.snap index 93b9f5ab..6c558d2f 100644 --- a/packages/haul-core/src/preset/__tests__/__snapshots__/makeConfigFactory.test.ts.snap +++ b/packages/haul-core/src/preset/__tests__/__snapshots__/makeConfigFactory.test.ts.snap @@ -19,7 +19,7 @@ Object { }, "external": false, "hasteOptions": Object {}, - "looseMode": [Function], + "looseMode": undefined, "maxWorkers": 7, "minify": false, "minifyOptions": undefined, @@ -46,7 +46,7 @@ Object { }, "external": false, "hasteOptions": Object {}, - "looseMode": [Function], + "looseMode": undefined, "maxWorkers": 7, "minify": false, "minifyOptions": undefined, @@ -75,7 +75,7 @@ Object { }, "external": false, "hasteOptions": Object {}, - "looseMode": [Function], + "looseMode": undefined, "maxWorkers": 7, "minify": false, "minifyOptions": undefined, @@ -142,28 +142,29 @@ Object { }, }, }, - RamBundleWebpackPlugin { - "bundleId": "app", - "bundleName": "app", - "indexRamBundle": true, - "maxWorkers": 7, - "minify": false, - "minifyOptions": undefined, + RamBundlePlugin { + "config": Object { + "bundleId": "app", + "bundleName": "app", + "bundlingMode": "multi-bundle", + "maxWorkers": 7, + "minify": false, + "minifyOptions": undefined, + "preloadBundles": Array [ + "base_dll", + ], + "sourceMap": true, + "type": "indexed-ram-bundle", + }, "modules": Array [], - "name": "RamBundleWebpackPlugin", - "preloadBundles": Array [ - "base_dll", - ], - "singleBundleMode": false, - "sourceMap": true, }, DefinePlugin { "definitions": Object { "process.env.HAUL_BUNDLES": "{\\"index\\":0,\\"base_dll\\":1,\\"app\\":2}", }, }, - LooseModeWebpackPlugin { - "checkLooseMode": [Function], + LooseModePlugin { + "shouldUseLoosMode": [Function], }, DllReferencePlugin { "options": Object { @@ -212,26 +213,27 @@ Object { }, }, }, - RamBundleWebpackPlugin { - "bundleId": "base_dll", - "bundleName": "base_dll", - "indexRamBundle": true, - "maxWorkers": 7, - "minify": false, - "minifyOptions": undefined, + RamBundlePlugin { + "config": Object { + "bundleId": "base_dll", + "bundleName": "base_dll", + "bundlingMode": "multi-bundle", + "maxWorkers": 7, + "minify": false, + "minifyOptions": undefined, + "preloadBundles": Array [], + "sourceMap": true, + "type": "indexed-ram-bundle", + }, "modules": Array [], - "name": "RamBundleWebpackPlugin", - "preloadBundles": Array [], - "singleBundleMode": false, - "sourceMap": true, }, DefinePlugin { "definitions": Object { "process.env.HAUL_BUNDLES": "{\\"index\\":0,\\"base_dll\\":1,\\"app\\":2}", }, }, - LooseModeWebpackPlugin { - "checkLooseMode": [Function], + LooseModePlugin { + "shouldUseLoosMode": [Function], }, DllPlugin { "options": Object { @@ -291,8 +293,8 @@ Object { "sourceMappingURLComment": " //# sourceMappingURL=[url]", }, - BasicBundleWebpackPlugin { - "preloadBundles": Array [ + PreloadBundlesPlugin { + "bundles": Array [ "base_dll", ], }, @@ -301,8 +303,8 @@ Object { "process.env.HAUL_BUNDLES": "{\\"index\\":0,\\"base_dll\\":1,\\"app\\":2}", }, }, - LooseModeWebpackPlugin { - "checkLooseMode": [Function], + LooseModePlugin { + "shouldUseLoosMode": [Function], }, DllReferencePlugin { "options": Object { @@ -337,7 +339,7 @@ Object { }, "external": false, "hasteOptions": Object {}, - "looseMode": [Function], + "looseMode": undefined, "maxWorkers": 7, "minify": false, "minifyOptions": undefined, @@ -364,7 +366,7 @@ Object { }, "external": false, "hasteOptions": Object {}, - "looseMode": [Function], + "looseMode": undefined, "maxWorkers": 7, "minify": false, "minifyOptions": undefined, @@ -393,7 +395,7 @@ Object { }, "external": false, "hasteOptions": Object {}, - "looseMode": [Function], + "looseMode": undefined, "maxWorkers": 7, "minify": false, "minifyOptions": undefined, @@ -474,8 +476,8 @@ Object { "sourceMappingURLComment": " //# sourceMappingURL=[url]", }, - BasicBundleWebpackPlugin { - "preloadBundles": Array [ + PreloadBundlesPlugin { + "bundles": Array [ "base_dll", ], }, @@ -484,8 +486,8 @@ Object { "process.env.HAUL_BUNDLES": "{\\"index\\":0,\\"base_dll\\":1,\\"app\\":2}", }, }, - LooseModeWebpackPlugin { - "checkLooseMode": [Function], + LooseModePlugin { + "shouldUseLoosMode": [Function], }, DllReferencePlugin { "options": Object { @@ -548,16 +550,16 @@ Object { "sourceMappingURLComment": " //# sourceMappingURL=[url]", }, - BasicBundleWebpackPlugin { - "preloadBundles": Array [], + PreloadBundlesPlugin { + "bundles": Array [], }, DefinePlugin { "definitions": Object { "process.env.HAUL_BUNDLES": "{\\"index\\":0,\\"base_dll\\":1,\\"app\\":2}", }, }, - LooseModeWebpackPlugin { - "checkLooseMode": [Function], + LooseModePlugin { + "shouldUseLoosMode": [Function], }, DllPlugin { "options": Object { @@ -617,8 +619,8 @@ Object { "sourceMappingURLComment": " //# sourceMappingURL=[url]", }, - BasicBundleWebpackPlugin { - "preloadBundles": Array [ + PreloadBundlesPlugin { + "bundles": Array [ "base_dll", ], }, @@ -627,8 +629,8 @@ Object { "process.env.HAUL_BUNDLES": "{\\"index\\":0,\\"base_dll\\":1,\\"app\\":2}", }, }, - LooseModeWebpackPlugin { - "checkLooseMode": [Function], + LooseModePlugin { + "shouldUseLoosMode": [Function], }, DllReferencePlugin { "options": Object { @@ -664,7 +666,7 @@ Object { }, "external": false, "hasteOptions": Object {}, - "looseMode": [Function], + "looseMode": undefined, "maxWorkers": 7, "minify": false, "minifyOptions": undefined, @@ -746,16 +748,16 @@ Object { "sourceMappingURLComment": " //# sourceMappingURL=[url]", }, - BasicBundleWebpackPlugin { - "preloadBundles": Array [], + PreloadBundlesPlugin { + "bundles": Array [], }, DefinePlugin { "definitions": Object { "process.env.HAUL_BUNDLES": "{\\"index\\":0}", }, }, - LooseModeWebpackPlugin { - "checkLooseMode": [Function], + LooseModePlugin { + "shouldUseLoosMode": [Function], }, ], "target": "webworker", diff --git a/packages/haul-core/src/preset/__tests__/makeConfigFactory.test.ts b/packages/haul-core/src/preset/__tests__/makeConfigFactory.test.ts index ce558ba1..0676cab7 100644 --- a/packages/haul-core/src/preset/__tests__/makeConfigFactory.test.ts +++ b/packages/haul-core/src/preset/__tests__/makeConfigFactory.test.ts @@ -4,7 +4,6 @@ import makeConfigFactory from '../makeConfigFactory'; import { Runtime, ProjectConfig, EnvOptions } from '../../'; // @ts-ignore import { replacePathsInObject } from 'jest/helpers'; // eslint-disable-line -import LooseModeWebpackPlugin from '../../webpack/plugins/LooseModeWebpackPlugin'; import withPolyfillsFactory from '../withPolyfillsFactory'; const makeConfig = makeConfigFactory( @@ -68,7 +67,7 @@ describe('makeConfig', () => { }; const config = makeConfig(projectConfig)(runtime, env); expect( - hasPlugin(config.webpackConfigs.index, 'BasicBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.index, 'PreloadBundlesPlugin') ).toBeTruthy(); expect(replacePathsInObject(config)).toMatchSnapshot(); }); @@ -138,7 +137,7 @@ describe('makeConfig', () => { }; const config = makeConfig(projectConfig)(runtime, env); expect( - hasPlugin(config.webpackConfigs.index, 'RamBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.index, 'RamBundlePlugin') ).toBeTruthy(); }); @@ -157,7 +156,7 @@ describe('makeConfig', () => { }; const config = makeConfig(projectConfig)(runtime, env); expect( - hasPlugin(config.webpackConfigs.index, 'RamBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.index, 'RamBundlePlugin') ).toBeTruthy(); }); @@ -213,10 +212,10 @@ describe('makeConfig', () => { }; const config = makeConfig(projectConfig)(runtime, env); expect( - hasPlugin(config.webpackConfigs.index, 'BasicBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.index, 'PreloadBundlesPlugin') ).toBeTruthy(); expect( - hasPlugin(config.webpackConfigs.index, 'RamBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.index, 'RamBundlePlugin') ).toBeFalsy(); }); @@ -317,13 +316,13 @@ describe('makeConfig', () => { }; const config = makeConfig(projectConfig)(runtime, env); expect( - hasPlugin(config.webpackConfigs.index, 'BasicBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.index, 'PreloadBundlesPlugin') ).toBeTruthy(); expect( - hasPlugin(config.webpackConfigs.base_dll, 'RamBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.base_dll, 'RamBundlePlugin') ).toBeTruthy(); expect( - hasPlugin(config.webpackConfigs.app, 'RamBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.app, 'RamBundlePlugin') ).toBeTruthy(); expect(replacePathsInObject(config)).toMatchSnapshot(); }); @@ -354,74 +353,15 @@ describe('makeConfig', () => { }; const config = makeConfig(projectConfig)(runtime, env); expect( - hasPlugin(config.webpackConfigs.index, 'BasicBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.index, 'PreloadBundlesPlugin') ).toBeTruthy(); expect( - hasPlugin(config.webpackConfigs.base_dll, 'BasicBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.base_dll, 'PreloadBundlesPlugin') ).toBeTruthy(); expect( - hasPlugin(config.webpackConfigs.app, 'BasicBundleWebpackPlugin') + hasPlugin(config.webpackConfigs.app, 'PreloadBundlesPlugin') ).toBeTruthy(); expect(replacePathsInObject(config)).toMatchSnapshot(); }); }); - - describe('with looseMode', () => { - const baseEnv: EnvOptions = { - platform: 'ios', - root: __dirname, - dev: false, - bundleMode: 'single-bundle', - bundleTarget: 'file', - }; - - it('should build all modules in loose mode', () => { - const projectConfig: ProjectConfig = { - bundles: { - index: { - entry: './index.js', - looseMode: true, - }, - }, - }; - const env: EnvOptions = { - ...baseEnv, - bundleTarget: 'server', - }; - const config = makeConfig(projectConfig)(runtime, env); - const looseModePlugin = config.webpackConfigs.index.plugins.find( - plugin => { - return plugin.constructor.name === 'LooseModeWebpackPlugin'; - } - ) as LooseModeWebpackPlugin; - expect(looseModePlugin.checkLooseMode('')).toBe(true); - }); - - it('should build some modules in loose mode', () => { - const moduleFilename = path.join(__dirname, 'someModule.js'); - - const projectConfig: ProjectConfig = { - bundles: { - index: { - entry: './index.js', - looseMode: [moduleFilename, /haul/], - }, - }, - }; - const env: EnvOptions = { - ...baseEnv, - bundleTarget: 'server', - }; - const config = makeConfig(projectConfig)(runtime, env); - const looseModePlugin = config.webpackConfigs.index.plugins.find( - plugin => { - return plugin.constructor.name === 'LooseModeWebpackPlugin'; - } - ) as LooseModeWebpackPlugin; - expect(looseModePlugin.checkLooseMode('')).toBe(false); - expect(looseModePlugin.checkLooseMode(moduleFilename)).toBe(true); - expect(looseModePlugin.checkLooseMode(__filename)).toBe(true); - expect(looseModePlugin.checkLooseMode('path')).toBe(false); - }); - }); }); diff --git a/packages/haul-core/src/preset/makeConfigFactory.ts b/packages/haul-core/src/preset/makeConfigFactory.ts index 9aadbe58..1ff318a5 100644 --- a/packages/haul-core/src/preset/makeConfigFactory.ts +++ b/packages/haul-core/src/preset/makeConfigFactory.ts @@ -23,10 +23,10 @@ import { } from '../config/types'; import applySingleBundleTweaks from './utils/applySingleBundleTweaks'; import applyMultiBundleTweaks from './utils/applyMultiBundleTweaks'; -import LooseModeWebpackPlugin from '../webpack/plugins/LooseModeWebpackPlugin'; -import InitCoreDllPlugin from '../webpack/plugins/InitCoreDllPlugin'; -import RamBundlePlugin from '../webpack/plugins/RamBundleWebpackPlugin'; -import BasicBundleWebpackPlugin from '../webpack/plugins/BasicBundleWebpackPlugin'; +import { LooseModePlugin } from '../webpack/plugins/LooseModePlugin'; +import { PreloadModulesDllPlugin } from '../webpack/plugins/PreloadModulesDllPlugin'; +import { RamBundlePlugin } from '../webpack/plugins/RamBundlePlugin'; +import { PreloadBundlesPlugin } from '../webpack/plugins/PreloadBundlesPlugin'; type GetDefaultConfig = ( runtime: Runtime, @@ -89,30 +89,6 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) { ? bundleConfigBuilder(env, runtime) : bundleConfigBuilder; - let looseMode: NormalizedBundleConfig['looseMode'] = () => false; - if (bundleConfig.looseMode === true) { - looseMode = () => true; - } else if (Array.isArray(bundleConfig.looseMode)) { - looseMode = (filename: string) => { - return (bundleConfig.looseMode as Array).some( - element => { - if (typeof element === 'string') { - if (!path.isAbsolute(element)) { - throw new Error( - `${element} in 'looseMode' property must be an absolute path or regex` - ); - } - return element === filename; - } - - return element.test(filename); - } - ); - }; - } else if (typeof bundleConfig.looseMode === 'function') { - looseMode = bundleConfig.looseMode; - } - // TODO: use minifyOptions to configure terser for basic bundle const dev = bundleConfig.dev || env.dev; const root = bundleConfig.root || env.root; @@ -142,7 +118,7 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) { typeof bundleConfig.sourceMap !== 'undefined' ? bundleConfig.sourceMap : true, - looseMode, + looseMode: bundleConfig.looseMode, app: Boolean(bundleConfig.app), dll: Boolean(bundleConfig.dll), dependsOn: bundleConfig.dependsOn || [], @@ -268,8 +244,8 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) { webpackConfig.plugins = (webpackConfig.plugins || []).concat( normalizedBundleConfig.type === 'basic-bundle' - ? new BasicBundleWebpackPlugin({ - preloadBundles: + ? new PreloadBundlesPlugin({ + bundles: featuresConfig.multiBundle === 1 ? normalizedBundleConfig.dependsOn : [], @@ -278,9 +254,8 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) { minify: normalizedBundleConfig.minify, minifyOptions: normalizedBundleConfig.minifyOptions, sourceMap: Boolean(normalizedBundleConfig.sourceMap), - indexRamBundle: - normalizedBundleConfig.type === 'indexed-ram-bundle', - singleBundleMode: env.bundleMode === 'single-bundle', + type: normalizedBundleConfig.type, + bundlingMode: env.bundleMode, preloadBundles: featuresConfig.multiBundle === 1 ? normalizedBundleConfig.dependsOn @@ -298,7 +273,7 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) { ); webpackConfig.plugins.push( - new LooseModeWebpackPlugin(normalizedBundleConfig.looseMode) + new LooseModePlugin(normalizedBundleConfig.looseMode || false) ); if ( @@ -307,8 +282,8 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) { featuresConfig.multiBundle >= 2 ) { webpackConfig.plugins.push( - new InitCoreDllPlugin({ - setupFiles: normalizedBundleConfig.entry.setupFiles, + new PreloadModulesDllPlugin({ + modules: normalizedBundleConfig.entry.setupFiles, }) ); } diff --git a/packages/haul-core/src/types.ts b/packages/haul-core/src/types.ts new file mode 100644 index 00000000..6f2c30bd --- /dev/null +++ b/packages/haul-core/src/types.ts @@ -0,0 +1,7 @@ +export type BundlingMode = 'single-bundle' | 'multi-bundle'; +export type RamBundleType = 'indexed-ram-bundle' | 'file-ram-bundle'; +export type BundleType = 'basic-bundle' | RamBundleType; +export type LooseModeConfig = + | boolean + | Array + | ((filename: string) => boolean); diff --git a/packages/haul-core/src/webpack/plugins/BasicBundleWebpackPlugin.ts b/packages/haul-core/src/webpack/plugins/BasicBundleWebpackPlugin.ts deleted file mode 100644 index 93a70e37..00000000 --- a/packages/haul-core/src/webpack/plugins/BasicBundleWebpackPlugin.ts +++ /dev/null @@ -1,29 +0,0 @@ -import webpack from 'webpack'; - -/** - * Adds React Native specific tweaks to bootstrap logic. - */ -export default class BasicBundleWebpackPlugin { - private preloadBundles: string[]; - - constructor({ preloadBundles }: { preloadBundles?: string[] }) { - this.preloadBundles = preloadBundles || []; - } - - apply(compiler: webpack.Compiler) { - compiler.hooks.compilation.tap('BasicBundleWebpackPlugin', compilation => { - (compilation.mainTemplate as any).hooks.bootstrap.tap( - 'BasicBundleWebpackPlugin', - (source: string) => { - const preload = this.preloadBundles.length - ? `${this.preloadBundles.map( - bundleName => - `if (!this["${bundleName}"]) { this.bundleRegistryLoad("${bundleName}", true, true); }\n` - )}\n` - : ''; - return `${preload}\n${source}`; - } - ); - }); - } -} diff --git a/packages/haul-core/src/webpack/plugins/InitCoreDllPlugin.ts b/packages/haul-core/src/webpack/plugins/InitCoreDllPlugin.ts deleted file mode 100644 index 7a4b8076..00000000 --- a/packages/haul-core/src/webpack/plugins/InitCoreDllPlugin.ts +++ /dev/null @@ -1,147 +0,0 @@ -import DllEntryDependency from 'webpack/lib/dependencies/DllEntryDependency'; -import DllModuleFactory from 'webpack/lib/DllModuleFactory'; -import DllModule from 'webpack/lib/DllModule'; -import { RawSource } from 'webpack-sources'; -import webpack from 'webpack'; -import { ResolverFactory } from 'enhanced-resolve'; - -class InitCoreDllModule extends DllModule { - constructor( - context: unknown, - dependencies: unknown, - name: unknown, - type: unknown, - private getInitCoreSource: () => string - ) { - super(context, dependencies, name, type); - } - - source() { - return new RawSource( - `${this.getInitCoreSource()}\nmodule.exports = __webpack_require__;` - ); - } -} - -class InitCoreDllModuleFactory extends DllModuleFactory { - constructor(private getInitCoreSource: () => string) { - super(); - } - - create( - data: { - dependencies: Array<{ dependencies: []; name: unknown; type: unknown }>; - context: unknown; - }, - callback: (error: Error | null, value: any) => void - ) { - const dependency = data.dependencies[0]; - callback( - null, - new InitCoreDllModule( - data.context, - dependency.dependencies, - dependency.name, - dependency.type, - this.getInitCoreSource - ) - ); - } -} - -export default class InitCoreDllPlugin { - private setupFiles: string[] = []; - private resolvedSetupFiles: string[] = []; - - constructor({ setupFiles }: { setupFiles: string[] }) { - this.setupFiles = setupFiles; - } - - apply(compiler: webpack.Compiler) { - compiler.hooks.beforeRun.tapPromise('InitCoreDllPlugin', async compiler => { - const resolver = ResolverFactory.createResolver({ - ...compiler.options.resolve, - fileSystem: compiler.inputFileSystem, - } as ResolverFactory.ResolverOption); - this.resolvedSetupFiles = await Promise.all( - this.setupFiles.map( - async setupFile => - new Promise((resolve, reject) => { - resolver.resolve( - {}, - compiler.context, - setupFile, - (error, resolved) => { - if (error) { - reject(error); - } else { - resolve(resolved); - } - } - ); - }) - ) - ); - }); - - compiler.hooks.compilation.intercept({ - register: tap => { - // Intercept tap from DllEntryPlugin in order to modify it. - // The modified tap function, will call original DllEntryPlugin tap function, - // and then overwrite the DllEntryDependency value in compilation.dependencyFactories - // with custom one. - if (tap.name === 'DllEntryPlugin') { - return { - ...tap, - fn: ( - compilation: webpack.compilation.Compilation, - ...args: any[] - ) => { - tap.fn(compilation, ...args); - const initCoreDllModuleFactory = new InitCoreDllModuleFactory( - () => { - const setupFilesModulesIds = compilation.modules.reduce( - (acc, webpackModule) => { - if ( - this.resolvedSetupFiles.some( - setupFile => webpackModule.resource === setupFile - ) - ) { - return { - ...acc, - [webpackModule.resource]: webpackModule.id, - }; - } - - return acc; - }, - {} - ); - - const setupCode = this.resolvedSetupFiles - .filter( - resolvedSetupFile => - setupFilesModulesIds[resolvedSetupFile] - ) - .map( - resolvedSetupFile => - `__webpack_require__(${JSON.stringify( - setupFilesModulesIds[resolvedSetupFile] - )});` - ) - .join('\n'); - return setupCode; - } - ); - compilation.dependencyFactories.set( - DllEntryDependency as any, - initCoreDllModuleFactory as any - ); - }, - }; - } - return tap; - }, - }); - } -} diff --git a/packages/haul-core/src/webpack/plugins/LooseModePlugin.ts b/packages/haul-core/src/webpack/plugins/LooseModePlugin.ts new file mode 100644 index 00000000..fa5420a8 --- /dev/null +++ b/packages/haul-core/src/webpack/plugins/LooseModePlugin.ts @@ -0,0 +1,76 @@ +import webpack from 'webpack'; +import sources from 'webpack-sources'; +import path from 'path'; +import { LooseModeConfig } from '../../types'; + +/** + * Enable JavaScript loose mode, by removing `use strict` directives from the code. + * This plugin should only be used for compatibility reasons with Metro, where some libraries + * might not work in JavaScript Strict mode. + */ +export class LooseModePlugin { + shouldUseLoosMode: (filename: string) => boolean; + + /** + * Constructs new `LooseModePlugin`. + * + * @param config - Plugin config value, can be either a: + * - `boolean` - enables or disables loose mode for all the modules within a bundle, + * - `string[]` - enables loose mode for only modules specified within the array, + * - `RegExp[]` - enables loose mode for only modules matching any of the regex within the array, + * - `(filename: string) => boolean` - enables loose mode for only modules, for which the function returns `true`. + */ + constructor(config: LooseModeConfig) { + if (config === true) { + this.shouldUseLoosMode = () => true; + } else if (Array.isArray(config)) { + this.shouldUseLoosMode = (filename: string) => { + return (config as Array).some(element => { + if (typeof element === 'string') { + if (!path.isAbsolute(element)) { + throw new Error( + `${element} in 'looseMode' property must be an absolute path or regex` + ); + } + return element === filename; + } + + return element.test(filename); + }); + }; + } else if (typeof config === 'function') { + this.shouldUseLoosMode = config; + } else { + this.shouldUseLoosMode = () => false; + } + } + + apply(compiler: webpack.Compiler) { + compiler.hooks.make.tap( + 'HaulLooseModePlugin', + (compilation: webpack.compilation.Compilation) => { + compilation.moduleTemplates.javascript.hooks.render.tap( + 'HaulLooseModePlugin', + ( + moduleSource: sources.Source, + { resource }: { resource: string } + ) => { + const useLooseMode = this.shouldUseLoosMode(resource); + if (!useLooseMode) { + return moduleSource; + } + + const source = moduleSource.source(); + const match = source.match(/['"]use strict['"]/); + if (!match || match.index === undefined) { + return moduleSource; + } + const replacement = new sources.ReplaceSource(moduleSource); + replacement.replace(match.index, match.index + match[0].length, ''); + return replacement; + } + ); + } + ); + } +} diff --git a/packages/haul-core/src/webpack/plugins/LooseModeWebpackPlugin.ts b/packages/haul-core/src/webpack/plugins/LooseModeWebpackPlugin.ts deleted file mode 100644 index 983dc3cd..00000000 --- a/packages/haul-core/src/webpack/plugins/LooseModeWebpackPlugin.ts +++ /dev/null @@ -1,35 +0,0 @@ -import webpack from 'webpack'; -import sources from 'webpack-sources'; - -export default class LooseModeWebpackPlugin { - constructor(public checkLooseMode: (filename: string) => boolean) {} - - apply(compiler: webpack.Compiler) { - compiler.hooks.make.tap( - 'LooseModeWebpackPlugin', - (compilation: webpack.compilation.Compilation) => { - compilation.moduleTemplates.javascript.hooks.render.tap( - 'LooseModeWebpackPlugin', - ( - moduleSource: sources.Source, - { resource }: { resource: string } - ) => { - const useLooseMode = this.checkLooseMode(resource); - if (!useLooseMode) { - return moduleSource; - } - - const source = moduleSource.source(); - const match = source.match(/['"]use strict['"]/); - if (!match || match.index === undefined) { - return moduleSource; - } - const replacement = new sources.ReplaceSource(moduleSource); - replacement.replace(match.index, match.index + match[0].length, ''); - return replacement; - } - ); - } - ); - } -} diff --git a/packages/haul-core/src/webpack/plugins/PreloadBundlesPlugin.ts b/packages/haul-core/src/webpack/plugins/PreloadBundlesPlugin.ts new file mode 100644 index 00000000..78521bbc --- /dev/null +++ b/packages/haul-core/src/webpack/plugins/PreloadBundlesPlugin.ts @@ -0,0 +1,34 @@ +import webpack from 'webpack'; + +/** + * Preload required bundles for multi-bundle v1 in bundle bootstrapping logic. + */ +export class PreloadBundlesPlugin { + private bundles: string[]; + + /** + * Constructs new `PreloadBundlesPlugin`. + * + * @param config - Config object with `bundles` array. Each value should represent a bundle name. + */ + constructor({ bundles }: { bundles?: string[] }) { + this.bundles = bundles || []; + } + + apply(compiler: webpack.Compiler) { + compiler.hooks.compilation.tap('HaulPreloadBundlesPlugin', compilation => { + (compilation.mainTemplate as any).hooks.bootstrap.tap( + 'HaulPreloadBundlesPlugin', + (source: string) => { + const preload = this.bundles.length + ? `${this.bundles.map( + bundleName => + `if (!this["${bundleName}"]) { this.bundleRegistryLoad("${bundleName}", true, true); }\n` + )}\n` + : ''; + return `${preload}\n${source}`; + } + ); + }); + } +} diff --git a/packages/haul-core/src/webpack/plugins/PreloadModulesDllPlugin.ts b/packages/haul-core/src/webpack/plugins/PreloadModulesDllPlugin.ts new file mode 100644 index 00000000..0c1784c0 --- /dev/null +++ b/packages/haul-core/src/webpack/plugins/PreloadModulesDllPlugin.ts @@ -0,0 +1,162 @@ +import DllEntryDependency from 'webpack/lib/dependencies/DllEntryDependency'; +import DllModuleFactory from 'webpack/lib/DllModuleFactory'; +import DllModule from 'webpack/lib/DllModule'; +import { RawSource } from 'webpack-sources'; +import webpack from 'webpack'; +import { ResolverFactory } from 'enhanced-resolve'; + +class PreloadDllModule extends DllModule { + constructor( + context: unknown, + dependencies: unknown, + name: unknown, + type: unknown, + private getPreloadSource: () => string + ) { + super(context, dependencies, name, type); + } + + source() { + return new RawSource( + `${this.getPreloadSource()}\nmodule.exports = __webpack_require__;` + ); + } +} + +class PreloadDllModuleFactory extends DllModuleFactory { + constructor(private getPreloadSource: () => string) { + super(); + } + + create( + data: { + dependencies: Array<{ dependencies: []; name: unknown; type: unknown }>; + context: unknown; + }, + callback: (error: Error | null, value: any) => void + ) { + const dependency = data.dependencies[0]; + callback( + null, + new PreloadDllModule( + data.context, + dependency.dependencies, + dependency.name, + dependency.type, + this.getPreloadSource + ) + ); + } +} + +/** + * For multi-bundle v2, the index (first) bundle will contain React Native code. + * If the bundle is a DLL bundle, then this code, which contain initialization logic will + * not be executed by default. + * + * This plugin allow to add imports for specified modules into the entry point of a DLL bundle, + * thus allowing to run the code that needs to be executed to setup environment correctly. + */ +export class PreloadModulesDllPlugin { + private modules: string[] = []; + private resolvedModules: string[] = []; + + /** + * Constructs new `PreloadModulesDllPlugin`. + * + * @param config - Config object with `modules` to import (thus execute) in DLL bundle entry point. + * Each value should be a path to a module, that will undergo standard Webpack resolution mechanism. + * It's recommended to use the same path as the one provided in `entry` array in Webpack config. + */ + constructor({ modules }: { modules: string[] }) { + this.modules = modules; + } + + apply(compiler: webpack.Compiler) { + compiler.hooks.beforeRun.tapPromise( + 'HaulPreloadModulesDllPlugin', + async compiler => { + const resolver = ResolverFactory.createResolver({ + ...compiler.options.resolve, + fileSystem: compiler.inputFileSystem, + } as ResolverFactory.ResolverOption); + this.resolvedModules = await Promise.all( + this.modules.map( + async modulePath => + new Promise((resolve, reject) => { + resolver.resolve( + {}, + compiler.context, + modulePath, + (error, resolved) => { + if (error) { + reject(error); + } else { + resolve(resolved); + } + } + ); + }) + ) + ); + } + ); + + compiler.hooks.compilation.intercept({ + register: tap => { + // Intercept tap from DllEntryPlugin in order to modify it. + // The modified tap function, will call original DllEntryPlugin tap function, + // and then overwrite the DllEntryDependency value in compilation.dependencyFactories + // with custom one. + if (tap.name === 'DllEntryPlugin') { + return { + ...tap, + fn: ( + compilation: webpack.compilation.Compilation, + ...args: any[] + ) => { + tap.fn(compilation, ...args); + const preloadDllModuleFactory = new PreloadDllModuleFactory( + () => { + const moduleIds = compilation.modules.reduce( + (acc, webpackModule) => { + if ( + this.resolvedModules.some( + modulePath => webpackModule.resource === modulePath + ) + ) { + return { + ...acc, + [webpackModule.resource]: webpackModule.id, + }; + } + + return acc; + }, + {} + ); + + const setupCode = this.resolvedModules + .filter(modulePath => moduleIds[modulePath]) + .map( + resolvedSetupFile => + `__webpack_require__(${JSON.stringify( + moduleIds[resolvedSetupFile] + )});` + ) + .join('\n'); + return setupCode; + } + ); + compilation.dependencyFactories.set( + DllEntryDependency as any, + preloadDllModuleFactory as any + ); + }, + }; + } + return tap; + }, + }); + } +} diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/FileRamBundle.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/FileRamBundle.ts similarity index 83% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/FileRamBundle.ts rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/FileRamBundle.ts index 4998c733..88ff9c1a 100644 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/FileRamBundle.ts +++ b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/FileRamBundle.ts @@ -2,15 +2,14 @@ import webpack from 'webpack'; import path from 'path'; import { RawSource } from 'webpack-sources'; import MAGIC_NUMBER from 'metro/src/shared/output/RamBundle/magic-number'; -import { Module } from './RamBundleWebpackPlugin'; +import { Module } from './RamBundlePlugin'; -export default class FileRamBundle { +export class FileRamBundle { constructor( public bootstrap: string, public modules: Module[], public sourceMap: boolean, - public bundleName: string, - public singleBundleMode: boolean + public outputPrefix: string ) {} build({ @@ -24,12 +23,11 @@ export default class FileRamBundle { sourceMapFilename: string; compilation: webpack.compilation.Compilation; }) { - const jsModulesDir = this.singleBundleMode - ? path.join(path.dirname(outputFilename), 'js-modules') - : path.join( - path.dirname(outputFilename), - `${this.bundleName}-js-modules` - ); + const jsModulesDir = path.join( + path.dirname(outputFilename), + `${this.outputPrefix}js-modules` + ); + // UNBUNDLE file tells React Native it's a RAM bundle. const UNBUNDLE = Buffer.alloc(4); UNBUNDLE.writeUInt32LE(MAGIC_NUMBER, 0); diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/IndexRamBundle.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/IndexRamBundle.ts similarity index 97% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/IndexRamBundle.ts rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/IndexRamBundle.ts index 3f889e3e..c9836dea 100644 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/IndexRamBundle.ts +++ b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/IndexRamBundle.ts @@ -1,7 +1,7 @@ import MAGIC_NUMBER from 'metro/src/shared/output/RamBundle/magic-number'; import webpack from 'webpack'; import { RawSource } from 'webpack-sources'; -import { Module } from './RamBundleWebpackPlugin'; +import { Module } from './RamBundlePlugin'; /*** * Reference: https://github.com/facebook/metro/blob/master/packages/metro/src/shared/output/RamBundle/as-indexed-file.js @@ -15,7 +15,7 @@ type ModuleBuffer = { buffer: Buffer; }; -export default class IndexRamBundle { +export class IndexRamBundle { encoding: 'ascii' | 'utf16le' | 'utf8' = 'utf8'; header: Buffer = Buffer.alloc(4); bootstrap: Buffer = Buffer.alloc(0); diff --git a/packages/haul-core/src/webpack/plugins/RamBundlePlugin/RamBundlePlugin.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/RamBundlePlugin.ts new file mode 100644 index 00000000..fadfe30d --- /dev/null +++ b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/RamBundlePlugin.ts @@ -0,0 +1,308 @@ +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import { inspect } from 'util'; +import webpack from 'webpack'; +import terser, { MinifyOptions } from 'terser'; +import Worker from 'jest-worker'; + +import { IndexRamBundle } from './IndexRamBundle'; +import { FileRamBundle } from './FileRamBundle'; +import { RamBundleType, BundlingMode } from '../../../types'; + +export type Module = { + id: string | number; + idx: number; + filename: string; + source: string; + map: Object; +}; + +type Compilation = webpack.compilation.Compilation & { + moduleTemplate: any; + options: webpack.Configuration; + mainTemplate: webpack.compilation.MainTemplate & { + renderBootstrap: Function; + hooks: webpack.compilation.CompilationHooks & { + renderWithEntry: { call: Function }; + }; + }; + moduleTemplates: { + javascript: webpack.compilation.ModuleTemplate & { + render: Function; + }; + }; +}; + +type ModuleMappings = { + modules: { [key: string]: number }; + chunks: { [key: string]: Array }; +}; + +type RamBundlePluginConfig = { + type: RamBundleType; + bundlingMode: BundlingMode; + sourceMap?: boolean; + preloadBundles?: string[]; + minify?: boolean; + minifyOptions?: Pick< + MinifyOptions, + Exclude + >; + maxWorkers: number; + bundleId: number | string; + bundleName: string; +}; + +const variableToString = (value?: string | number) => { + if (value === undefined) { + return 'undefined'; + } + return typeof value === 'string' ? `"${value}"` : value.toString(); +}; + +/** + * Generate RAM bundle instead of standard (plain JS) bundle. + * Supports both Indexed and File RAM bundle formats. + */ +export class RamBundlePlugin { + private modules: Module[] = []; + + /** + * Constructs new `RamBundlePlugin`. + * + * @param config - An object with plugin configuration: + * - `type` - type of the bundle - either `file-ram-bundle` or `indexed-ram-bundle`, + * - `bundlingMode` - whether the bundle is a part of multiple ones - `multi-bundle` or not - `single-bundle`, + * - `sourceMap`- whether to generate source maps, + * - `preloadBundles` - bundle names to preload in bootstrapping code for `multi-bundle` v1, + * - `minify` - whether to minify the code, + * - `minifyOptions` - Terser minify options (without `sourceMap` property, which is provided automatically), + * - `maxWorkers` - number of worker threads to use, + * - `bundleId` - bundle id for `multi-bundle` v2, + * - `bundleName`- bundle name for `multi-bundle` v1/v2, + */ + constructor(private config: RamBundlePluginConfig) {} + + private async onEmit(compilation: Compilation) { + const moduleMappings: ModuleMappings = { + modules: {}, + chunks: {}, + }; + + let mainId: string | number | undefined; + let mainChunk: webpack.compilation.Chunk | undefined; + + (compilation.chunks as webpack.compilation.Chunk[]).forEach(chunk => { + if ((chunk.id as any) === 'main' || chunk.name === 'main') { + mainChunk = chunk; + mainId = chunk.entryModule?.id ?? undefined; + } + + chunk.getModules().forEach(moduleInChunk => { + if (chunk.id !== null) { + moduleMappings.chunks[chunk.id] = ([] as Array) + .concat(...(moduleMappings.chunks[chunk.id] || [])) + .concat(moduleInChunk.id ?? []); + } + }); + }); + + // Omit chunks mapping if there's only a single main chunk + if (compilation.chunks.length === 1) { + moduleMappings.chunks = {}; + } + + if (mainId === undefined) { + throw new Error( + "RamBundlePlugin: couldn't find main chunk's entry module id" + ); + } + if (!mainChunk) { + throw new Error("RamBundlePlugin: couldn't find main chunk"); + } + // Render modules to it's 'final' form with injected webpack variables + // and wrapped with ModuleTemplate. + const minifyWorker = new Worker( + require.resolve( + '../../../../build/webpack/plugins/RamBundlePlugin/minifyWorker' + ), + { + numWorkers: this.config.maxWorkers, + enableWorkerThreads: true, + } + ); + + this.modules = await Promise.all( + compilation.modules.map(async webpackModule => { + const renderedModule = compilation.moduleTemplates.javascript + .render( + webpackModule, + compilation.dependencyTemplates, + compilation.options + ) + .sourceAndMap(); + + if (typeof webpackModule.id === 'string') { + moduleMappings.modules[webpackModule.id] = webpackModule.index; + } + + let code = `__haul_${this.config.bundleName}.l(${variableToString( + webpackModule.id + )}, ${renderedModule.source});`; + let map = renderedModule.map; + + if (this.config.minify) { + const minifyOptionsWithMap = { + ...this.config.minifyOptions, + sourceMap: { + content: map, + }, + }; + // @ts-ignore property minify does not exist on type 'JestWorker' + const minifiedSource = await minifyWorker.minify( + code, + minifyOptionsWithMap + ); + //Check if there is no error in minifed source + assert(!minifiedSource.error, minifiedSource.error); + + code = minifiedSource.code || ''; + if (typeof minifiedSource.map === 'string') { + map = JSON.parse(minifiedSource.map); + } + } + + return { + id: webpackModule.id, + idx: webpackModule.index, + filename: webpackModule.resource, + source: code, + map: { + ...map, + file: `${ + typeof webpackModule.id === 'string' + ? webpackModule.index + : webpackModule.id + }.js`, + }, + }; + }) + ); + + minifyWorker.end(); + + const indent = (line: string) => `/*****/ ${line}`; + let bootstrap = fs.readFileSync( + path.join( + __dirname, + '../../../../runtime/webpack/plugin/ram-bundle-webpack-plugin/bootstrap.js' + ), + 'utf8' + ); + if ( + typeof this.config.bundleName !== 'string' || + !this.config.bundleName.length + ) { + throw new Error( + 'RamBundleWebpackPlugin: bundle name cannot be empty string' + ); + } + + bootstrap = + `(${bootstrap.trim()})(this, ${inspect( + { + bundleName: this.config.bundleName, + bundleId: this.config.bundleId, + mainModuleId: mainId, + preloadBundleNames: this.config.preloadBundles || [], + singleBundleMode: this.config.bundlingMode === 'single-bundle', + moduleMappings, + }, + { + depth: null, + maxArrayLength: null, + breakLength: Infinity, + } + )});` + .split('\n') + .map(indent) + .join('\n') + '\n'; + + // Enhance bootstrapCode with additional JS from plugins that hook + // into `renderWithEntry` for example: webpack's `library` is used here to expose + // bundle as a library. + const renderWithEntryResults = compilation.mainTemplate.hooks.renderWithEntry.call( + bootstrap, + mainChunk, + mainChunk.hash + ); + if (typeof renderWithEntryResults === 'string') { + bootstrap = renderWithEntryResults; + } else if ('source' in renderWithEntryResults) { + bootstrap = renderWithEntryResults.source(); + } + + if (this.config.minify) { + const { error, code } = terser.minify(bootstrap); + if (error) { + throw error; + } + bootstrap = code || ''; + } + + const outputFilename = compilation.outputOptions.filename!; + const outputDest = path.isAbsolute(outputFilename) + ? outputFilename + : path.join(compilation.outputOptions.path, outputFilename); + + const sourceMapFilename = compilation.getPath( + compilation.outputOptions.sourceMapFilename, + { + filename: path.isAbsolute(outputFilename) + ? path.relative(compilation.context, outputFilename) + : outputFilename, + } + ); + + const bundle = + this.config.type === 'indexed-ram-bundle' + ? new IndexRamBundle(bootstrap, this.modules, this.config.sourceMap) + : new FileRamBundle( + bootstrap, + this.modules, + Boolean(this.config.sourceMap), + this.config.bundlingMode === 'multi-bundle' + ? `${this.config.bundleName}-` + : '' + ); + + const filesToRemove: string[] = compilation.chunks.reduce((acc, chunk) => { + if (chunk.name !== 'main') { + return [...acc, ...chunk.files]; + } + return acc; + }, []); + Object.keys(compilation.assets).forEach(assetName => { + const remove = filesToRemove.some(file => assetName.endsWith(file)); + if (remove) { + delete compilation.assets[assetName]; + } + }); + + bundle.build({ + outputDest, + outputFilename, + sourceMapFilename, + compilation, + }); + } + + apply(compiler: webpack.Compiler) { + compiler.hooks.emit.tapPromise('HaulRamBundlePlugin', async compilation => { + // Cast compilation from @types/webpack to custom Compilation type + // which contains additional properties. + await this.onEmit(compilation as any); + }); + } +} diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/FileRamBundle.test.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/FileRamBundle.test.ts similarity index 88% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/FileRamBundle.test.ts rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/FileRamBundle.test.ts index 08540f01..39c6a35f 100644 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/FileRamBundle.test.ts +++ b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/FileRamBundle.test.ts @@ -1,6 +1,6 @@ import { RawSource } from 'webpack-sources'; import MAGIC_NUMBER from 'metro/src/shared/output/RamBundle/magic-number'; -import FileRamBundle from '../FileRamBundle'; +import { FileRamBundle } from '../FileRamBundle'; test('FileRamBundle should create valid RAM bundle', () => { const bootstrapper = '(function() { /* bootstrap */ })()'; @@ -22,13 +22,7 @@ test('FileRamBundle should create valid RAM bundle', () => { ]; const compilation = { assets: {} as { [key: string]: RawSource } }; - const ramBundle = new FileRamBundle( - bootstrapper, - modules, - false, - 'index', - true - ); + const ramBundle = new FileRamBundle(bootstrapper, modules, false, ''); ramBundle.build({ outputDest: '', outputFilename: 'main.jsbundle', diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/IndexRamBundle.test.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/IndexRamBundle.test.ts similarity index 95% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/IndexRamBundle.test.ts rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/IndexRamBundle.test.ts index 082b930b..18739465 100644 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/IndexRamBundle.test.ts +++ b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/IndexRamBundle.test.ts @@ -1,6 +1,6 @@ import { RawSource } from 'webpack-sources'; import RamBundleParser from 'metro/src/lib/RamBundleParser'; -import IndexRamBundle from '../IndexRamBundle'; +import { IndexRamBundle } from '../IndexRamBundle'; test('IndexRamBundle should create valid RAM bundle', () => { const bootstrapper = '(function() { /* bootstrap */ })()'; diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/crashFn.async.js b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/crashFn.async.js similarity index 100% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/crashFn.async.js rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/crashFn.async.js diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/hello.cjs.js b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/hello.cjs.js similarity index 100% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/hello.cjs.js rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/hello.cjs.js diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/hello.esm.js b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/hello.esm.js similarity index 100% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/hello.esm.js rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/hello.esm.js diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/index.js b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/index.js similarity index 100% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/index.js rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/index.js diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/nestedCrashFn.esm.js b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/nestedCrashFn.esm.js similarity index 100% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/nestedCrashFn.esm.js rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/nestedCrashFn.esm.js diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/transpiled.js b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/transpiled.js similarity index 100% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/__fixtures__/transpiled.js rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/__fixtures__/transpiled.js diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/integration.test.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/integration.test.ts similarity index 95% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/integration.test.ts rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/integration.test.ts index bdf41d43..ee1a2712 100644 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/__tests__/integration.test.ts +++ b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/__tests__/integration.test.ts @@ -5,7 +5,8 @@ import fs from 'fs'; import assert from 'assert'; import { SourceMapConsumer } from 'source-map'; import RamBundleParser from 'metro/src/lib/RamBundleParser'; -import RamBundlePlugin from '../RamBundleWebpackPlugin'; +import { RamBundlePlugin } from '../RamBundlePlugin'; +import { RamBundleType } from '../../../../types'; function wait(ms: number) { return new Promise(resolve => { @@ -18,12 +19,12 @@ const BUNDLE_PATH = path.join(__dirname, './__fixtures__/dist/main.jsbundle'); function build({ namedModules, sourceMap, - indexRamBundle, + type, }: { namedModules?: boolean; sourceMap?: boolean; - indexRamBundle?: boolean; -} = {}) { + type: RamBundleType; +}) { const compiler = webpack({ entry: path.join(__dirname, './__fixtures__/index.js'), mode: 'development', @@ -53,9 +54,10 @@ function build({ plugins: [ new RamBundlePlugin({ sourceMap, - indexRamBundle, + type, minify: false, maxWorkers: 1, + bundlingMode: 'single-bundle', bundleName: 'index', bundleId: 'index', }), @@ -109,7 +111,7 @@ describe('Indexed RAM bundle should build and successfully evaluate bundle', () }); it('with namedModules:false', async () => { - await build({ indexRamBundle: true }); + await build({ type: 'indexed-ram-bundle' }); const bundle = fs.readFileSync(BUNDLE_PATH); const parser = new RamBundleParser(bundle); @@ -151,7 +153,7 @@ describe('Indexed RAM bundle should build and successfully evaluate bundle', () }); it('with namedModules:true', async () => { - await build({ namedModules: true, indexRamBundle: true }); + await build({ namedModules: true, type: 'indexed-ram-bundle' }); const bundle = fs.readFileSync(BUNDLE_PATH); const parser = new RamBundleParser(bundle); @@ -185,7 +187,7 @@ describe('Indexed RAM bundle should build and successfully evaluate bundle', () }); it('with source maps', async () => { - await build({ sourceMap: true, indexRamBundle: true }); + await build({ sourceMap: true, type: 'indexed-ram-bundle' }); const bundle = fs.readFileSync(BUNDLE_PATH); const indexSourceMap = JSON.parse( @@ -253,7 +255,7 @@ describe('File RAM bundle should build and successfully evaluate bundle', () => }); it('with namedModules:false', async () => { - await build(); + await build({ type: 'file-ram-bundle' }); const bootstrap = fs.readFileSync(BUNDLE_PATH).toString(); expect(bootstrap).toBeDefined(); @@ -297,7 +299,7 @@ describe('File RAM bundle should build and successfully evaluate bundle', () => }); it('with namedModules:true', async () => { - await build({ namedModules: true }); + await build({ namedModules: true, type: 'file-ram-bundle' }); const bootstrap = fs.readFileSync(BUNDLE_PATH).toString(); expect(bootstrap).toBeDefined(); @@ -333,7 +335,7 @@ describe('File RAM bundle should build and successfully evaluate bundle', () => }); it('with source maps', async () => { - await build({ sourceMap: true }); + await build({ sourceMap: true, type: 'file-ram-bundle' }); const indexSourceMap = JSON.parse( fs.readFileSync(`${BUNDLE_PATH}.map`).toString() diff --git a/packages/haul-core/src/webpack/plugins/RamBundlePlugin/index.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/index.ts new file mode 100644 index 00000000..8fecf0cd --- /dev/null +++ b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/index.ts @@ -0,0 +1 @@ +export { RamBundlePlugin } from './RamBundlePlugin'; diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/worker.ts b/packages/haul-core/src/webpack/plugins/RamBundlePlugin/minifyWorker.ts similarity index 100% rename from packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/worker.ts rename to packages/haul-core/src/webpack/plugins/RamBundlePlugin/minifyWorker.ts diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/RamBundleWebpackPlugin.ts b/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/RamBundleWebpackPlugin.ts deleted file mode 100644 index d081b4fb..00000000 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/RamBundleWebpackPlugin.ts +++ /dev/null @@ -1,324 +0,0 @@ -import assert from 'assert'; -import fs from 'fs'; -import path from 'path'; -import { inspect } from 'util'; -import webpack from 'webpack'; -import terser, { MinifyOptions } from 'terser'; -import Worker from 'jest-worker'; - -import IndexRamBundle from './IndexRamBundle'; -import FileRamBundle from './FileRamBundle'; - -export type Module = { - id: string | number; - idx: number; - filename: string; - source: string; - map: Object; -}; - -type Compilation = webpack.compilation.Compilation & { - moduleTemplate: any; - options: webpack.Configuration; - mainTemplate: webpack.compilation.MainTemplate & { - renderBootstrap: Function; - hooks: webpack.compilation.CompilationHooks & { - renderWithEntry: { call: Function }; - }; - }; - moduleTemplates: { - javascript: webpack.compilation.ModuleTemplate & { - render: Function; - }; - }; -}; - -type ModuleMappings = { - modules: { [key: string]: number }; - chunks: { [key: string]: Array }; -}; - -type RamBundleWebpackPluginOptions = { - singleBundleMode?: boolean; - sourceMap?: boolean; - indexRamBundle?: boolean; - preloadBundles?: string[]; - minify?: boolean; - minifyOptions?: Pick< - MinifyOptions, - Exclude - >; - maxWorkers: number; - bundleId: number | string; - bundleName: string; -}; - -const variableToString = (value?: string | number) => { - if (value === undefined) { - return 'undefined'; - } - return typeof value === 'string' ? `"${value}"` : value.toString(); -}; - -const hasValue = (value: any): boolean => - typeof value === 'undefined' ? false : true; - -export default class RamBundleWebpackPlugin { - name = 'RamBundleWebpackPlugin'; - - modules: Module[] = []; - sourceMap: boolean = false; - indexRamBundle: boolean = true; - preloadBundles: string[]; - bundleId: number | string = 'index'; - bundleName: string = 'index'; - singleBundleMode: boolean = true; - minify: boolean = false; - minifyOptions: RamBundleWebpackPluginOptions['minifyOptions'] = undefined; - maxWorkers: number; - - constructor({ - sourceMap, - indexRamBundle, - preloadBundles, - singleBundleMode, - minify, - minifyOptions, - maxWorkers, - bundleId, - bundleName, - }: RamBundleWebpackPluginOptions) { - this.sourceMap = Boolean(sourceMap); - this.indexRamBundle = Boolean(indexRamBundle); - this.preloadBundles = preloadBundles || []; - this.singleBundleMode = hasValue(singleBundleMode) - ? Boolean(singleBundleMode) - : this.singleBundleMode; - this.minify = hasValue(minify) ? Boolean(minify) : this.minify; - this.minifyOptions = minifyOptions; - this.maxWorkers = maxWorkers; - this.bundleId = bundleId; - this.bundleName = bundleName; - } - - apply(compiler: webpack.Compiler) { - compiler.hooks.emit.tapPromise( - 'RamBundleWebpackPlugin', - async _compilation => { - // Cast compilation from @types/webpack to custom Compilation type - // which contains additional properties. - const compilation: Compilation = _compilation as any; - - const moduleMappings: ModuleMappings = { - modules: {}, - chunks: {}, - }; - - let mainId: string | number | undefined; - let mainChunk: webpack.compilation.Chunk | undefined; - - (compilation.chunks as webpack.compilation.Chunk[]).forEach(chunk => { - if ((chunk.id as any) === 'main' || chunk.name === 'main') { - mainChunk = chunk; - mainId = chunk.entryModule?.id ?? undefined; - } - - chunk.getModules().forEach(moduleInChunk => { - if (chunk.id !== null) { - moduleMappings.chunks[chunk.id] = ([] as Array) - .concat(...(moduleMappings.chunks[chunk.id] || [])) - .concat(moduleInChunk.id ?? []); - } - }); - }); - - // Omit chunks mapping if there's only a single main chunk - if (compilation.chunks.length === 1) { - moduleMappings.chunks = {}; - } - - if (mainId === undefined) { - throw new Error( - "RamBundleWebpackPlugin: couldn't find main chunk's entry module id" - ); - } - if (!mainChunk) { - throw new Error("RamBundleWebpackPlugin: couldn't find main chunk"); - } - // Render modules to it's 'final' form with injected webpack variables - // and wrapped with ModuleTemplate. - const minifyWorker = new Worker( - require.resolve( - '../../../../build/webpack/plugins/RamBundleWebpackPlugin/worker' - ), - { - numWorkers: this.maxWorkers, - enableWorkerThreads: true, - } - ); - - this.modules = await Promise.all( - compilation.modules.map(async webpackModule => { - const renderedModule = compilation.moduleTemplates.javascript - .render( - webpackModule, - compilation.dependencyTemplates, - compilation.options - ) - .sourceAndMap(); - - if (typeof webpackModule.id === 'string') { - moduleMappings.modules[webpackModule.id] = webpackModule.index; - } - - let code = `__haul_${this.bundleName}.l(${variableToString( - webpackModule.id - )}, ${renderedModule.source});`; - let map = renderedModule.map; - - if (this.minify) { - const minifyOptionsWithMap = { - ...this.minifyOptions, - sourceMap: { - content: map, - }, - }; - // @ts-ignore property minify does not exist on type 'JestWorker' - const minifiedSource = await minifyWorker.minify( - code, - minifyOptionsWithMap - ); - //Check if there is no error in minifed source - assert(!minifiedSource.error, minifiedSource.error); - - code = minifiedSource.code || ''; - if (typeof minifiedSource.map === 'string') { - map = JSON.parse(minifiedSource.map); - } - } - - return { - id: webpackModule.id, - idx: webpackModule.index, - filename: webpackModule.resource, - source: code, - map: { - ...map, - file: `${ - typeof webpackModule.id === 'string' - ? webpackModule.index - : webpackModule.id - }.js`, - }, - }; - }) - ); - - minifyWorker.end(); - - const indent = (line: string) => `/*****/ ${line}`; - let bootstrap = fs.readFileSync( - path.join( - __dirname, - '../../../../runtime/webpack/plugin/ram-bundle-webpack-plugin/bootstrap.js' - ), - 'utf8' - ); - if (typeof this.bundleName !== 'string' || !this.bundleName.length) { - throw new Error( - 'RamBundleWebpackPlugin: bundle name cannot be empty string' - ); - } - - bootstrap = - `(${bootstrap.trim()})(this, ${inspect( - { - bundleName: this.bundleName, - bundleId: this.bundleId, - mainModuleId: mainId, - preloadBundleNames: this.preloadBundles, - singleBundleMode: this.singleBundleMode, - moduleMappings, - }, - { - depth: null, - maxArrayLength: null, - breakLength: Infinity, - } - )});` - .split('\n') - .map(indent) - .join('\n') + '\n'; - - // Enhance bootstrapCode with additional JS from plugins that hook - // into `renderWithEntry` for example: webpack's `library` is used here to expose - // bundle as a library. - const renderWithEntryResults = compilation.mainTemplate.hooks.renderWithEntry.call( - bootstrap, - mainChunk, - mainChunk.hash - ); - if (typeof renderWithEntryResults === 'string') { - bootstrap = renderWithEntryResults; - } else if ('source' in renderWithEntryResults) { - bootstrap = renderWithEntryResults.source(); - } - - if (this.minify) { - const { error, code } = terser.minify(bootstrap); - if (error) { - throw error; - } - bootstrap = code || ''; - } - - const outputFilename = compilation.outputOptions.filename!; - const outputDest = path.isAbsolute(outputFilename) - ? outputFilename - : path.join(compilation.outputOptions.path, outputFilename); - - const sourceMapFilename = compilation.getPath( - compilation.outputOptions.sourceMapFilename, - { - filename: path.isAbsolute(outputFilename) - ? path.relative(compilation.context, outputFilename) - : outputFilename, - } - ); - - const bundle = this.indexRamBundle - ? new IndexRamBundle(bootstrap, this.modules, this.sourceMap) - : new FileRamBundle( - bootstrap, - this.modules, - this.sourceMap, - this.bundleName, - this.singleBundleMode - ); - - const filesToRemove: string[] = compilation.chunks.reduce( - (acc, chunk) => { - if (chunk.name !== 'main') { - return [...acc, ...chunk.files]; - } - return acc; - }, - [] - ); - Object.keys(compilation.assets).forEach(assetName => { - const remove = filesToRemove.some(file => assetName.endsWith(file)); - if (remove) { - delete compilation.assets[assetName]; - } - }); - - bundle.build({ - outputDest, - outputFilename, - sourceMapFilename, - compilation, - }); - } - ); - } -} diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/index.ts b/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/index.ts deleted file mode 100644 index 4676811b..00000000 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './RamBundleWebpackPlugin'; diff --git a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/utils.ts b/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/utils.ts deleted file mode 100644 index e8bcc012..00000000 --- a/packages/haul-core/src/webpack/plugins/RamBundleWebpackPlugin/utils.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const countLines = (string: string) => - (string.match(/\r\n?|\n|\u2028|\u2029/g) || []).length; diff --git a/packages/haul-core/src/webpack/plugins/__tests__/LooseModePlugin.test.ts b/packages/haul-core/src/webpack/plugins/__tests__/LooseModePlugin.test.ts new file mode 100644 index 00000000..bbe5672c --- /dev/null +++ b/packages/haul-core/src/webpack/plugins/__tests__/LooseModePlugin.test.ts @@ -0,0 +1,71 @@ +import webpack from 'webpack'; +import path from 'path'; +import fs from 'fs'; +import del from 'del'; +import { LooseModePlugin } from '../LooseModePlugin'; + +async function build(plugin: webpack.Plugin) { + const FILENAME = 'bundle.js'; + const DIR = path.join(__dirname, './__fixtures__/tmp'); + + del.sync(DIR); + + return new Promise((resolve, reject) => { + webpack({ + mode: 'development', + entry: path.join(__dirname, './__fixtures__/index.js'), + output: { + path: DIR, + filename: 'bundle.js', + }, + plugins: [plugin], + }).run(error => { + if (error) { + reject(error); + } else { + const bundle = fs.readFileSync(path.join(DIR, FILENAME)).toString(); + del.sync(DIR); + resolve(bundle); + } + }); + }); +} + +describe('LooseModePlugin', () => { + it('should remove all use strict', async () => { + const bundle = await build(new LooseModePlugin(() => true)); + expect(bundle).not.toMatch('use strict'); + }); + + it('should not remove any use strict', async () => { + const bundle = await build(new LooseModePlugin(() => false)); + + expect(bundle).toMatch('use strict'); + expect((bundle.match(/use strict/g) || []).length).toBe(2); + }); + + it('should remove use strict based on array with files', async () => { + const bundle = await build( + new LooseModePlugin([path.join(__dirname, './__fixtures__/index.js')]) + ); + + expect(bundle).toMatch('use strict'); + expect((bundle.match(/use strict/g) || []).length).toBe(1); + const indexModuleCode = bundle.substr( + bundle.indexOf('__fixtures__/index.js ***!') + ); + expect(indexModuleCode).not.toMatch('use strict'); + }); + + it('should remove use strict based on array with regex', async () => { + const bundle = await build(new LooseModePlugin([/async/])); + + expect(bundle).toMatch('use strict'); + expect((bundle.match(/use strict/g) || []).length).toBe(1); + const asyncModuleCode = bundle.substring( + bundle.indexOf('__fixtures__/async.js ***!'), + bundle.indexOf('__fixtures__/index.js ***!') + ); + expect(asyncModuleCode).not.toMatch('use strict'); + }); +}); diff --git a/packages/haul-core/src/webpack/plugins/__tests__/LooseModeWebpackPlugin.test.ts b/packages/haul-core/src/webpack/plugins/__tests__/LooseModeWebpackPlugin.test.ts deleted file mode 100644 index 8e858ac7..00000000 --- a/packages/haul-core/src/webpack/plugins/__tests__/LooseModeWebpackPlugin.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import webpack from 'webpack'; -import path from 'path'; -import fs from 'fs'; -import del from 'del'; -import LooseModeWebpackPlugin from '../LooseModeWebpackPlugin'; - -async function build(plugin: webpack.Plugin) { - const FILENAME = 'bundle.js'; - const DIR = path.join(__dirname, './__fixtures__/tmp'); - - del.sync(DIR); - - return new Promise((resolve, reject) => { - webpack({ - mode: 'development', - entry: path.join(__dirname, './__fixtures__/index.js'), - output: { - path: DIR, - filename: 'bundle.js', - }, - plugins: [plugin], - }).run(error => { - if (error) { - reject(error); - } else { - const bundle = fs.readFileSync(path.join(DIR, FILENAME)).toString(); - del.sync(DIR); - resolve(bundle); - } - }); - }); -} - -describe('LooseModeWebpackPlugin', () => { - it('should remove use strict', async () => { - const bundle = await build(new LooseModeWebpackPlugin(() => true)); - expect(bundle).not.toMatch('use strict'); - }); -}); diff --git a/packages/haul-core/src/webpack/plugins/__tests__/WebpackBasicBundlePlugin.test.ts b/packages/haul-core/src/webpack/plugins/__tests__/PreloadBundlesPlugin.test.ts similarity index 90% rename from packages/haul-core/src/webpack/plugins/__tests__/WebpackBasicBundlePlugin.test.ts rename to packages/haul-core/src/webpack/plugins/__tests__/PreloadBundlesPlugin.test.ts index 0f9d07c2..670c5efa 100644 --- a/packages/haul-core/src/webpack/plugins/__tests__/WebpackBasicBundlePlugin.test.ts +++ b/packages/haul-core/src/webpack/plugins/__tests__/PreloadBundlesPlugin.test.ts @@ -1,4 +1,4 @@ -import BasicBundleWebpackPlugin from '../BasicBundleWebpackPlugin'; +import { PreloadBundlesPlugin } from '../PreloadBundlesPlugin'; import webpack from 'webpack'; import fs from 'fs'; import path from 'path'; @@ -47,8 +47,8 @@ describe('BasicBundleWebpackPlugin', () => { const bundle = await build({ ...getConfig(), plugins: [ - new BasicBundleWebpackPlugin({ - preloadBundles: ['test_bundle'], + new PreloadBundlesPlugin({ + bundles: ['test_bundle'], }), ], }); diff --git a/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/async.js b/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/async.js index 5eb03c6c..9757a3e6 100644 --- a/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/async.js +++ b/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/async.js @@ -1 +1,3 @@ +'use strict'; + export default () => console.log('async'); diff --git a/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/entry.js b/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/entry.js index ac3d0dd6..f7e3b75f 100644 --- a/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/entry.js +++ b/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/entry.js @@ -1,3 +1,5 @@ +'use strict'; + console.log('entry'); import('./async').then(m => m.default()); diff --git a/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/index.js b/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/index.js index 8ad7cfc8..3b1ca000 100644 --- a/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/index.js +++ b/packages/haul-core/src/webpack/plugins/__tests__/__fixtures__/index.js @@ -1,5 +1,7 @@ 'use strict'; +import './async'; + const str = 'string'; str.someMethod = function() {}