Skip to content

Commit c99f301

Browse files
zamotanythymikee
andauthored
feat: add LooseModeWebpackPlugin and looseMode config plugin (#673)
Co-Authored-By: Michał Pierzchała <[email protected]>
1 parent 1dad8cd commit c99f301

File tree

10 files changed

+239
-0
lines changed

10 files changed

+239
-0
lines changed

docs/Configuration.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ The bundle configuration consist of the following properties:
106106
- `dependsOn?: string[]` (_optional_, **multi-bundle mode only**) - Specify on which DLLs the bundle depends on, which will be automatically linked when building the bundle.
107107
- `providesModuleNodeModules?: Array<string | { name: string; directory: string }>` - Provide custom modules for Haste.
108108
- `hasteOptions?: any` - Provide Haste options.
109+
- `looseMode?: true | Array<string | RegExp> | ((filename: string) => boolean)` - Removes `'use strict';` from:
110+
- the whole bundle if `true`
111+
- modules matched by absolute filename if array of string is passed (can be mixed with regexes)
112+
- modules matched by the the regexes if array of regexes is passed (can be mixed with strings)
113+
- modules for which function `(filename: string) => boolean` returns `true`
109114
- `transform?: WebpackConfigTransform` - Customize the Webpack config after it's been created.
110115
111116
The `transform` function will receive an object with `bundleName`, `env`, `runtime` and Webpack's `config`:

docs/Recipes.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,40 @@ Set the environment variable `APP_ENV` to `detox_tests` when running Haul:
215215
APP_ENV=detox_tests yarn haul
216216
```
217217

218+
## Using with Lottie or loose-mode-only-compatible libraries
219+
220+
Some React Native libraries might not work with Haul out of the box. If the library if throw error, it is possible that it's not compatible with strict mode checks.
221+
222+
By default all modules with Haul have `'use strict';` annotation, which makes the code evaluated in strict mode, whereas the default React Native bundler - Metro - generates the code without this annotation. For that reason, some libraries eg `Lottie` need special steps to work.
223+
224+
To enable loose mode, you need to add `looseMode` property to your config set to:
225+
226+
- `true` - removes all `'use strict';` from the whole bundle
227+
- array for absolute filenames of modules - matched modules will have `'use strict';` removed
228+
- array of regexes - matched modules will have `'use strict';` removed
229+
- function that accepts absolute module filename and must return `true` to remove `'use strict';` or `false`
230+
231+
You can mix string and regexes in array.
232+
233+
For example:
234+
235+
```js
236+
// haul.config.js
237+
import { makeConfig, withPolyfills } from "@haul-bundler/preset-0.59";
238+
239+
export default makeConfig({
240+
bundles: {
241+
index: {
242+
entry: withPolyfills('./index.ts'),
243+
looseMode: [
244+
require.resolve('./MyModule.js'),
245+
/node_modules\/react-native-lottie/,
246+
],
247+
},
248+
},
249+
});
250+
```
251+
218252
## Debugging with React Native Tools (`vscode-react-native`)
219253

220254
In order to use React Native Tools extension you must first install the extension:

packages/haul-core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"terser": "^4.1.3",
5353
"utility-types": "^3.7.0",
5454
"webpack": "^4.39.1",
55+
"webpack-sources": "^1.4.1",
5556
"wrap-ansi": "^6.0.0",
5657
"ws": "^6.2.1"
5758
},

packages/haul-core/src/config/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export type BundleConfig = Assign<
4040
Exclude<keyof MinifyOptions, 'sourceMap'>
4141
>;
4242
sourceMap?: boolean | 'inline';
43+
looseMode?: true | Array<string | RegExp> | ((filename: string) => boolean);
4344
dll?: boolean;
4445
app?: boolean;
4546
dependsOn?: string[];
@@ -82,6 +83,7 @@ export type NormalizedBundleConfig = Assign<
8283
DeepNonNullable<ExternalBundleConfig>,
8384
{ manifestPath: ExternalBundleConfig['manifestPath'] }
8485
>;
86+
looseMode: (filename: string) => boolean;
8587
}
8688
>;
8789

packages/haul-core/src/preset/__tests__/__snapshots__/makeConfigFactory.test.ts.snap

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Object {
1616
],
1717
"external": false,
1818
"hasteOptions": Object {},
19+
"looseMode": [Function],
1920
"minify": false,
2021
"minifyOptions": undefined,
2122
"name": "app",
@@ -38,6 +39,7 @@ Object {
3839
],
3940
"external": false,
4041
"hasteOptions": Object {},
42+
"looseMode": [Function],
4143
"minify": false,
4244
"minifyOptions": undefined,
4345
"name": "base_dll",
@@ -62,6 +64,7 @@ Object {
6264
],
6365
"external": false,
6466
"hasteOptions": Object {},
67+
"looseMode": [Function],
6568
"minify": false,
6669
"minifyOptions": undefined,
6770
"name": "index",
@@ -151,6 +154,9 @@ Object {
151154
"singleBundleMode": false,
152155
"sourceMap": true,
153156
},
157+
LooseModeWebpackPlugin {
158+
"checkLooseMode": [Function],
159+
},
154160
DllReferencePlugin {
155161
"options": Object {
156162
"context": "<<REPLACED>>/packages/haul-core/src/preset/__tests__",
@@ -220,6 +226,9 @@ Object {
220226
"singleBundleMode": false,
221227
"sourceMap": true,
222228
},
229+
LooseModeWebpackPlugin {
230+
"checkLooseMode": [Function],
231+
},
223232
DllPlugin {
224233
"options": Object {
225234
"name": "base_dll",
@@ -282,6 +291,9 @@ Object {
282291
],
283292
"sourceMap": true,
284293
},
294+
LooseModeWebpackPlugin {
295+
"checkLooseMode": [Function],
296+
},
285297
DllReferencePlugin {
286298
"options": Object {
287299
"context": "<<REPLACED>>/packages/haul-core/src/preset/__tests__",
@@ -312,6 +324,7 @@ Object {
312324
],
313325
"external": false,
314326
"hasteOptions": Object {},
327+
"looseMode": [Function],
315328
"minify": false,
316329
"minifyOptions": undefined,
317330
"name": "app",
@@ -334,6 +347,7 @@ Object {
334347
],
335348
"external": false,
336349
"hasteOptions": Object {},
350+
"looseMode": [Function],
337351
"minify": false,
338352
"minifyOptions": undefined,
339353
"name": "base_dll",
@@ -358,6 +372,7 @@ Object {
358372
],
359373
"external": false,
360374
"hasteOptions": Object {},
375+
"looseMode": [Function],
361376
"minify": false,
362377
"minifyOptions": undefined,
363378
"name": "index",
@@ -441,6 +456,9 @@ Object {
441456
],
442457
"sourceMap": true,
443458
},
459+
LooseModeWebpackPlugin {
460+
"checkLooseMode": [Function],
461+
},
444462
DllReferencePlugin {
445463
"options": Object {
446464
"context": "<<REPLACED>>/packages/haul-core/src/preset/__tests__",
@@ -504,6 +522,9 @@ Object {
504522
"preloadBundles": Array [],
505523
"sourceMap": true,
506524
},
525+
LooseModeWebpackPlugin {
526+
"checkLooseMode": [Function],
527+
},
507528
DllPlugin {
508529
"options": Object {
509530
"name": "base_dll",
@@ -566,6 +587,9 @@ Object {
566587
],
567588
"sourceMap": true,
568589
},
590+
LooseModeWebpackPlugin {
591+
"checkLooseMode": [Function],
592+
},
569593
DllReferencePlugin {
570594
"options": Object {
571595
"context": "<<REPLACED>>/packages/haul-core/src/preset/__tests__",
@@ -594,6 +618,7 @@ Object {
594618
],
595619
"external": false,
596620
"hasteOptions": Object {},
621+
"looseMode": [Function],
597622
"minify": false,
598623
"minifyOptions": undefined,
599624
"name": "index",
@@ -673,6 +698,9 @@ Object {
673698
"preloadBundles": Array [],
674699
"sourceMap": true,
675700
},
701+
LooseModeWebpackPlugin {
702+
"checkLooseMode": [Function],
703+
},
676704
],
677705
"target": "webworker",
678706
},

packages/haul-core/src/preset/__tests__/makeConfigFactory.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import makeConfigFactory from '../makeConfigFactory';
44
import { Runtime, ProjectConfig, EnvOptions } from '../../';
55
// @ts-ignore
66
import { replacePathsInObject } from 'jest/helpers'; // eslint-disable-line
7+
import LooseModeWebpackPlugin from '../../webpack/plugins/LooseModeWebpackPlugin';
78

89
const makeConfig = makeConfigFactory(
910
(_runtime, env, bundleName, projectConfig) => ({
@@ -354,4 +355,63 @@ describe('makeConfig', () => {
354355
expect(replacePathsInObject(config)).toMatchSnapshot();
355356
});
356357
});
358+
359+
describe('with looseMode', () => {
360+
const baseEnv: EnvOptions = {
361+
platform: 'ios',
362+
root: __dirname,
363+
dev: false,
364+
bundleMode: 'single-bundle',
365+
bundleTarget: 'file',
366+
};
367+
368+
it('should build all modules in loose mode', () => {
369+
const projectConfig: ProjectConfig = {
370+
bundles: {
371+
index: {
372+
entry: './index.js',
373+
looseMode: true,
374+
},
375+
},
376+
};
377+
const env: EnvOptions = {
378+
...baseEnv,
379+
bundleTarget: 'server',
380+
};
381+
const config = makeConfig(projectConfig)(runtime, env);
382+
const looseModePlugin = config.webpackConfigs.index.plugins.find(
383+
plugin => {
384+
return plugin.constructor.name === 'LooseModeWebpackPlugin';
385+
}
386+
) as LooseModeWebpackPlugin;
387+
expect(looseModePlugin.checkLooseMode('')).toBe(true);
388+
});
389+
390+
it('should build some modules in loose mode', () => {
391+
const moduleFilename = path.join(__dirname, 'someModule.js');
392+
393+
const projectConfig: ProjectConfig = {
394+
bundles: {
395+
index: {
396+
entry: './index.js',
397+
looseMode: [moduleFilename, /haul/],
398+
},
399+
},
400+
};
401+
const env: EnvOptions = {
402+
...baseEnv,
403+
bundleTarget: 'server',
404+
};
405+
const config = makeConfig(projectConfig)(runtime, env);
406+
const looseModePlugin = config.webpackConfigs.index.plugins.find(
407+
plugin => {
408+
return plugin.constructor.name === 'LooseModeWebpackPlugin';
409+
}
410+
) as LooseModeWebpackPlugin;
411+
expect(looseModePlugin.checkLooseMode('')).toBe(false);
412+
expect(looseModePlugin.checkLooseMode(moduleFilename)).toBe(true);
413+
expect(looseModePlugin.checkLooseMode(__filename)).toBe(true);
414+
expect(looseModePlugin.checkLooseMode('path')).toBe(false);
415+
});
416+
});
357417
});

packages/haul-core/src/preset/makeConfigFactory.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import applySingleBundleTweaks from './utils/applySingleBundleTweaks';
2222
import applyMultiBundleTweaks from './utils/applyMultiBundleTweaks';
2323
import getBundlePlugin from './utils/getBundlePlugin';
24+
import LooseModeWebpackPlugin from '../webpack/plugins/LooseModeWebpackPlugin';
2425

2526
type GetDefaultConfig = (
2627
runtime: Runtime,
@@ -73,6 +74,30 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) {
7374
? bundleConfigBuilder(env, runtime)
7475
: bundleConfigBuilder;
7576

77+
let looseMode: NormalizedBundleConfig['looseMode'] = () => false;
78+
if (bundleConfig.looseMode === true) {
79+
looseMode = () => true;
80+
} else if (Array.isArray(bundleConfig.looseMode)) {
81+
looseMode = (filename: string) => {
82+
return (bundleConfig.looseMode as Array<string | RegExp>).some(
83+
element => {
84+
if (typeof element === 'string') {
85+
if (!path.isAbsolute(element)) {
86+
throw new Error(
87+
`${element} in 'looseMode' property must be an absolute path or regex`
88+
);
89+
}
90+
return element === filename;
91+
}
92+
93+
return element.test(filename);
94+
}
95+
);
96+
};
97+
} else if (typeof bundleConfig.looseMode === 'function') {
98+
looseMode = bundleConfig.looseMode;
99+
}
100+
76101
// TODO: use minifyOptions to configure terser for basic bundle
77102
const dev = bundleConfig.dev || env.dev;
78103
const root = bundleConfig.root || env.root;
@@ -97,6 +122,7 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) {
97122
typeof bundleConfig.sourceMap !== 'undefined'
98123
? bundleConfig.sourceMap
99124
: true,
125+
looseMode,
100126
app: Boolean(bundleConfig.app),
101127
dll: Boolean(bundleConfig.dll),
102128
dependsOn: bundleConfig.dependsOn || [],
@@ -205,6 +231,10 @@ export default function makeConfigFactory(getDefaultConfig: GetDefaultConfig) {
205231
getBundlePlugin(env, normalizedBundleConfig)
206232
);
207233

234+
webpackConfig.plugins.push(
235+
new LooseModeWebpackPlugin(normalizedBundleConfig.looseMode)
236+
);
237+
208238
if (env.bundleMode === 'single-bundle') {
209239
applySingleBundleTweaks(
210240
env,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import webpack from 'webpack';
2+
import sources from 'webpack-sources';
3+
4+
export default class LooseModeWebpackPlugin {
5+
constructor(public checkLooseMode: (filename: string) => boolean) {}
6+
7+
apply(compiler: webpack.Compiler) {
8+
compiler.hooks.make.tap(
9+
'LooseModeWebpackPlugin',
10+
(compilation: webpack.compilation.Compilation) => {
11+
compilation.moduleTemplates.javascript.hooks.render.tap(
12+
'LooseModeWebpackPlugin',
13+
(
14+
moduleSource: sources.Source,
15+
{ resource }: { resource: string }
16+
) => {
17+
const useLooseMode = this.checkLooseMode(resource);
18+
if (!useLooseMode) {
19+
return moduleSource;
20+
}
21+
22+
const source = moduleSource.source();
23+
const match = source.match(/['"]use strict['"]/);
24+
if (!match || match.index === undefined) {
25+
return moduleSource;
26+
}
27+
const replacement = new sources.ReplaceSource(moduleSource);
28+
replacement.replace(match.index, match.index + match[0].length, '');
29+
return replacement;
30+
}
31+
);
32+
}
33+
);
34+
}
35+
}

0 commit comments

Comments
 (0)