@taro-hmr-compile/plugin-webpack 0.0.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/compile/compile-state.d.ts +3 -0
  2. package/dist/compile/compile-state.js +21 -0
  3. package/dist/compile/compile-state.js.map +1 -0
  4. package/dist/compile/hmr-compiler-loader.d.ts +9 -0
  5. package/dist/compile/hmr-compiler-loader.js +199 -0
  6. package/dist/compile/hmr-compiler-loader.js.map +1 -0
  7. package/dist/compile/hmr-compiler-plugin.d.ts +46 -0
  8. package/dist/compile/hmr-compiler-plugin.js +173 -0
  9. package/dist/compile/hmr-compiler-plugin.js.map +1 -0
  10. package/dist/compile/lazy-compiler-loader.d.ts +18 -0
  11. package/dist/compile/lazy-compiler-loader.js +38 -0
  12. package/dist/compile/lazy-compiler-loader.js.map +1 -0
  13. package/dist/constants.d.ts +4 -0
  14. package/dist/constants.js +8 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.js +68 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/log.d.ts +3 -0
  20. package/dist/log.js +11 -0
  21. package/dist/log.js.map +1 -0
  22. package/dist/patch/hmr-patch-plugin.d.ts +17 -0
  23. package/dist/patch/hmr-patch-plugin.js +93 -0
  24. package/dist/patch/hmr-patch-plugin.js.map +1 -0
  25. package/dist/patch/hmr-patch-processor.d.ts +30 -0
  26. package/dist/patch/hmr-patch-processor.js +122 -0
  27. package/dist/patch/hmr-patch-processor.js.map +1 -0
  28. package/dist/patch/hmr-patch-runtime-plugin.d.ts +4 -0
  29. package/dist/patch/hmr-patch-runtime-plugin.js +113 -0
  30. package/dist/patch/hmr-patch-runtime-plugin.js.map +1 -0
  31. package/dist/patch/types.d.ts +16 -0
  32. package/dist/patch/types.js +3 -0
  33. package/dist/patch/types.js.map +1 -0
  34. package/dist/patch/utils.d.ts +28 -0
  35. package/dist/patch/utils.js +179 -0
  36. package/dist/patch/utils.js.map +1 -0
  37. package/dist/server/hmr-server-plugin.d.ts +13 -0
  38. package/dist/server/hmr-server-plugin.js +42 -0
  39. package/dist/server/hmr-server-plugin.js.map +1 -0
  40. package/dist/utils.d.ts +4 -0
  41. package/dist/utils.js +25 -0
  42. package/dist/utils.js.map +1 -0
  43. package/package.json +58 -0
  44. package/src/compile/compile-state.ts +15 -0
  45. package/src/compile/hmr-compiler-loader.ts +177 -0
  46. package/src/compile/hmr-compiler-plugin.ts +233 -0
  47. package/src/compile/lazy-compiler-loader.ts +57 -0
  48. package/src/constants.ts +7 -0
  49. package/src/index.ts +81 -0
  50. package/src/log.ts +5 -0
  51. package/src/patch/hmr-patch-plugin.ts +111 -0
  52. package/src/patch/hmr-patch-processor.ts +169 -0
  53. package/src/patch/hmr-patch-runtime-plugin.ts +119 -0
  54. package/src/patch/types.ts +20 -0
  55. package/src/patch/utils.ts +179 -0
  56. package/src/server/hmr-server-plugin.ts +40 -0
  57. package/src/utils.ts +25 -0
  58. package/tsconfig.json +21 -0
@@ -0,0 +1,119 @@
1
+ import {parse} from '@babel/parser'
2
+ import generate from '@babel/generator'
3
+ import webpack from 'webpack'
4
+
5
+ const PLUGIN_NAME = 'HmrPatchRuntimePlugin'
6
+
7
+ const hotRuntimeCode = `
8
+ var applyQueue = Promise.resolve();
9
+
10
+ ${webpack.RuntimeGlobals.require}.miniHmr = {
11
+ shouldApplyBootstrapPatch: function(patch) {
12
+ if (!patch) return false;
13
+
14
+ return Object.keys(patch.modules).some(function(moduleId) {
15
+ return !Object.prototype.hasOwnProperty.call(${webpack.RuntimeGlobals.moduleFactories}, moduleId);
16
+ });
17
+ },
18
+ applyBootstrapPatch: function(patch) {
19
+ if (!patch) return [];
20
+
21
+ // 微信要求 Page() 在页面脚本求值期间同步注册。bootstrap 只补齐缺失工厂,
22
+ // 不会覆盖现有模块;必须在 Page Chunk startup 继续执行前同步完成。
23
+ var installedModules = [];
24
+ Object.keys(patch.modules).forEach(function(moduleId) {
25
+ if (!Object.prototype.hasOwnProperty.call(${webpack.RuntimeGlobals.moduleFactories}, moduleId)) {
26
+ ${webpack.RuntimeGlobals.moduleFactories}[moduleId] = patch.modules[moduleId];
27
+ installedModules.push(moduleId);
28
+ }
29
+ });
30
+ return installedModules;
31
+ },
32
+ applyUpdatePatch: function(patch) {
33
+ if (!patch) return Promise.resolve();
34
+
35
+ globalThis.log('apply update patch %O', patch);
36
+
37
+ var task = applyQueue.then(function() {
38
+ var runtimeHash = ${webpack.RuntimeGlobals.getFullHash}();
39
+
40
+ if (runtimeHash === patch.toHash) return;
41
+
42
+ currentUpdateApplyHandlers = [];
43
+
44
+ ${webpack.RuntimeGlobals.require}.miniHmr.preparePatch(patch, currentUpdateApplyHandlers);
45
+
46
+ return setStatus('ready')
47
+ .then(function() { return hotApply(); })
48
+ .then(function(updatedModules) {
49
+ ${webpack.RuntimeGlobals.getFullHash} = function() { return patch.toHash; };
50
+ return updatedModules;
51
+ });
52
+ });
53
+
54
+ // 单次失败不能阻塞后续 patch
55
+ applyQueue = task.catch(function() {});
56
+
57
+ return task;
58
+ }
59
+ };
60
+ `;
61
+
62
+ const chunkLoadingRuntimeCode = `
63
+ ${webpack.RuntimeGlobals.require}.miniHmr.preparePatch = function(patch, applyHandlers) {
64
+ currentUpdateChunks = {};
65
+ currentUpdate = Object.assign({}, patch.modules);
66
+ currentUpdateRemovedChunks = [];
67
+ currentUpdateRuntime = patch.runtimeModules ? patch.runtimeModules.slice() : [];
68
+
69
+ if (patch.removedModules) {
70
+ patch.removedModules.forEach(function(moduleId) {
71
+ currentUpdate[moduleId] = false;
72
+ });
73
+ }
74
+
75
+ applyHandlers.push(applyHandler);
76
+ };
77
+ `
78
+
79
+ const injectRuntime = (source: string, injectedCode: string) => {
80
+ const runtime = parse(source, {sourceType: 'script'})
81
+ const injected = parse(injectedCode, {sourceType: 'script'})
82
+
83
+ runtime.program.body.push(...injected.program.body)
84
+
85
+ return generate(runtime).code
86
+ }
87
+
88
+ export class HmrPatchRuntimePlugin {
89
+ apply(compiler: webpack.Compiler) {
90
+ compiler.hooks.thisCompilation.tap(
91
+ PLUGIN_NAME,
92
+ compilation => {
93
+ compilation.hooks.additionalTreeRuntimeRequirements.tap(PLUGIN_NAME, (_chunk, runtimeRequirements) => {
94
+ runtimeRequirements.add(webpack.RuntimeGlobals.require)
95
+ runtimeRequirements.add(webpack.RuntimeGlobals.getFullHash)
96
+ })
97
+
98
+ compilation.hooks.runtimeModule.tap(PLUGIN_NAME, runtimeModule => {
99
+ const injectedCode = {
100
+ HotModuleReplacementRuntimeModule: hotRuntimeCode,
101
+ JsonpChunkLoadingRuntimeModule: chunkLoadingRuntimeCode,
102
+ }[runtimeModule.constructor.name]
103
+
104
+ if (!injectedCode) return
105
+
106
+ const generate = runtimeModule.generate.bind(runtimeModule)
107
+
108
+ runtimeModule.generate = () => {
109
+ const source = generate()
110
+
111
+ if (!source) return source
112
+
113
+ return injectRuntime(source, injectedCode)
114
+ }
115
+ })
116
+ },
117
+ )
118
+ }
119
+ }
@@ -0,0 +1,20 @@
1
+ import type {Expression} from "@babel/types";
2
+
3
+ export interface ModuleFactory {
4
+ factory: Expression,
5
+ moduleId: string
6
+ }
7
+
8
+ export interface HotUpdateManifest {
9
+ m?: Array<string | number>
10
+ }
11
+
12
+ export interface UpdatePatchSource {
13
+ type: 'update'
14
+ fromHash: string
15
+ toHash: string
16
+ moduleFactories: ModuleFactory[]
17
+ runtimeModules: Expression[]
18
+ removedModules: string[]
19
+ }
20
+
@@ -0,0 +1,179 @@
1
+ import * as t from "@babel/types";
2
+ import {parse} from "@babel/parser";
3
+ import traverse from "@babel/traverse";
4
+ import template from "@babel/template";
5
+ import generator from "@babel/generator";
6
+ import {Asset} from "webpack";
7
+ import {HotUpdateManifest, ModuleFactory} from "./types";
8
+ import type {ObjectExpression} from "@babel/types";
9
+ import {UpdatePatchSource} from "./types";
10
+
11
+
12
+ /**
13
+ * 解析模块 Factory
14
+ * @param expression
15
+ */
16
+ const parseModuleFactories = (expression: ObjectExpression) => {
17
+
18
+ const moduleFactories = new Map<string, ModuleFactory>()
19
+
20
+ if (!t.isObjectExpression(expression)) return Array.from(moduleFactories.values())
21
+
22
+ expression.properties.forEach(property => {
23
+ if (!t.isObjectProperty(property) || !t.isExpression(property.value)) throw new Error('[taro-hmr-compile] HMR 模块对象包含无法识别的属性')
24
+
25
+ const {key, value: factory} = property
26
+
27
+ const moduleId = (() => {
28
+ if (t.isStringLiteral(key) || t.isNumericLiteral(key)) return String(key.value)
29
+ if (t.isIdentifier(key) && !property.computed) return key.name
30
+ throw new Error('[taro-hmr-compile] Chunk 模块对象包含无法识别的模块 ID')
31
+ })()
32
+
33
+ moduleFactories.set(moduleId, {factory, moduleId})
34
+ })
35
+
36
+ return Array.from(moduleFactories.values())
37
+ }
38
+
39
+
40
+ export interface ParseHotUpdateAssetsOptions {
41
+ manifests: Readonly<Asset>[]
42
+ scripts: Readonly<Asset>[]
43
+ }
44
+
45
+ /**
46
+ * 解析热更新产物
47
+ * @param opt
48
+ */
49
+ export const parseHotUpdateAssets = (opt: ParseHotUpdateAssetsOptions) => {
50
+ const {manifests, scripts} = opt
51
+
52
+ const moduleFactories = new Map<string, ModuleFactory>()
53
+ const runtimeModules: t.Expression[] = []
54
+
55
+ scripts.forEach(asset => {
56
+
57
+ const ast = parse(asset.source.source().toString(), {sourceType: 'script'})
58
+
59
+ traverse(ast, {
60
+ CallExpression(nodePath) {
61
+ const {callee} = nodePath.node
62
+
63
+ if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.object, {name: 'wx'})) return
64
+
65
+ if (!t.isStringLiteral(callee.property) || !callee.property.value.startsWith('webpackHotUpdate')) return
66
+
67
+ const [, modules, runtime] = nodePath.node.arguments
68
+
69
+ if (!t.isObjectExpression(modules)) return
70
+
71
+ parseModuleFactories(modules).forEach(module => moduleFactories.set(module.moduleId, module))
72
+ if (t.isExpression(runtime)) runtimeModules.push(runtime)
73
+ },
74
+ })
75
+ })
76
+
77
+ const removedModules = new Set<string>()
78
+
79
+ manifests.forEach((module) => {
80
+
81
+ const manifest = JSON.parse(module.source.source().toString()) as HotUpdateManifest
82
+
83
+ const {m: tempRemoveModules = []} = manifest
84
+
85
+ tempRemoveModules.forEach(removedModule => removedModules.add(String(removedModule)))
86
+ }, [])
87
+
88
+ return {moduleFactories, runtimeModules, removedModules}
89
+ }
90
+
91
+ /**
92
+ * 解析 chunk 产物
93
+ * @param source
94
+ */
95
+ export const parseChunkAsset = (source: string): Map<string, ModuleFactory> => {
96
+ const ast = parse(`(${source})`, {sourceType: 'script'})
97
+
98
+ const [statement] = ast.program.body
99
+
100
+ if (!t.isExpressionStatement(statement) || !t.isObjectExpression(statement.expression)) throw new Error('[taro-hmr-compile] Chunk 模块段不是对象表达式')
101
+
102
+ const moduleFactories = new Map<string, ModuleFactory>()
103
+
104
+ parseModuleFactories(statement.expression).forEach(module => moduleFactories.set(module.moduleId, module))
105
+
106
+ return moduleFactories
107
+ }
108
+
109
+ export const mergeUpdatePatches = (basePatch: UpdatePatchSource, nextPatch: UpdatePatchSource,): UpdatePatchSource => {
110
+ if (basePatch.toHash !== nextPatch.fromHash) throw new Error(`[taro-hmr-compile] 无法合并不连续的 HMR patch:${basePatch.toHash} !== ${nextPatch.fromHash}`)
111
+
112
+ const moduleFactories = new Map(basePatch.moduleFactories.map(moduleFactory => [moduleFactory.moduleId, moduleFactory]))
113
+
114
+ const removedModules = new Set(basePatch.removedModules)
115
+
116
+ nextPatch.moduleFactories.forEach(moduleFactory => {
117
+ const {moduleId} = moduleFactory
118
+ moduleFactories.set(moduleId, moduleFactory)
119
+ removedModules.delete(moduleId)
120
+ })
121
+
122
+ nextPatch.removedModules.forEach(moduleId => {
123
+ moduleFactories.delete(moduleId)
124
+ removedModules.add(moduleId)
125
+ })
126
+
127
+ return {
128
+ type: 'update',
129
+ fromHash: basePatch.fromHash,
130
+ toHash: nextPatch.toHash,
131
+ moduleFactories: Array.from(moduleFactories.values()),
132
+ runtimeModules: [...basePatch.runtimeModules, ...nextPatch.runtimeModules],
133
+ removedModules: [...removedModules],
134
+ }
135
+ }
136
+
137
+ export interface generatePatchAssetSourceOpts {
138
+ bootstrapFactories: Map<string, ModuleFactory>
139
+ recentUpdatePatches: UpdatePatchSource[]
140
+ }
141
+
142
+ export const generatePatchAssetSource = (opt: generatePatchAssetSourceOpts): string => {
143
+ const {bootstrapFactories, recentUpdatePatches} = opt
144
+ const buildBootstrapPatch = template.expression(`({
145
+ type: 'bootstrap',
146
+ modules: MODULES
147
+ })`)
148
+ const buildUpdatePatch = template.expression(`({
149
+ type: 'update',
150
+ fromHash: "FROM_HASH",
151
+ toHash: "TO_HASH",
152
+ modules: MODULES,
153
+ runtimeModules: RUNTIME_MODULES,
154
+ removedModules: REMOVED_MODULES
155
+ })`)
156
+ const buildPatchAsset = template.program('module.exports = PATCHES;')
157
+ const bootstrapModules = Array.from(bootstrapFactories.values()).map((moduleFactory) => {
158
+ const {moduleId, factory} = moduleFactory
159
+ return t.objectProperty(t.stringLiteral(moduleId), factory)
160
+ })
161
+ const patchNodes = [
162
+ buildBootstrapPatch({MODULES: t.objectExpression(bootstrapModules)}),
163
+ ...recentUpdatePatches.map(patch => {
164
+ const updateModules = patch.moduleFactories.map((moduleFactory) => {
165
+ const {moduleId, factory} = moduleFactory
166
+ return t.objectProperty(t.stringLiteral(moduleId), factory)
167
+ })
168
+ return buildUpdatePatch({
169
+ FROM_HASH: patch.fromHash,
170
+ TO_HASH: patch.toHash,
171
+ MODULES: t.objectExpression(updateModules),
172
+ RUNTIME_MODULES: t.arrayExpression(patch.runtimeModules),
173
+ REMOVED_MODULES: t.arrayExpression(patch.removedModules.map(moduleId => t.stringLiteral(moduleId))),
174
+ })
175
+ }),
176
+ ]
177
+
178
+ return generator(buildPatchAsset({PATCHES: t.arrayExpression(patchNodes)})).code
179
+ }
@@ -0,0 +1,40 @@
1
+ import {HmrCompileServer} from '@taro-hmr-compile/core/compiler'
2
+ import type {Compiler} from 'webpack'
3
+ import {HmrCompilerPlugin} from '../compile/hmr-compiler-plugin'
4
+ import {log} from "../log";
5
+
6
+ const serverLog = log.extend('HmrServerPlugin')
7
+
8
+ const PLUGIN_NAME = 'HmrServerPlugin'
9
+
10
+ export interface HmrServerPluginOptions {
11
+ host?: string
12
+ port?: number
13
+ }
14
+
15
+ export class HmrServerPlugin extends HmrCompileServer {
16
+ private compiler?: Compiler
17
+
18
+ constructor(private readonly options: HmrServerPluginOptions) {
19
+ super()
20
+ }
21
+
22
+ apply(compiler: Compiler) {
23
+ this.compiler = compiler
24
+
25
+ compiler.hooks.beforeCompile.tapPromise(PLUGIN_NAME, async () => {
26
+ if (this.isServerRunning) return
27
+ const {port = 8080, host} = this.options
28
+ serverLog('start server %O', {port, host})
29
+ await this.start(port, host)
30
+ })
31
+
32
+ compiler.hooks.watchClose.tap(PLUGIN_NAME, () => this.stop())
33
+ }
34
+
35
+ override compile(page: string) {
36
+ serverLog('receive compile %s', page)
37
+ if (!this.compiler) throw new Error('[taro-hmr-compile] Compiler 尚未初始化')
38
+ HmrCompilerPlugin.getCompilerHooks(this.compiler).compileRequested.call(page)
39
+ }
40
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,25 @@
1
+ import {readConfig, resolveMainFilePath} from '@tarojs/helper'
2
+ import type {IPluginContext} from '@tarojs/service'
3
+ import type {AppConfig} from '@tarojs/taro'
4
+ import path from 'path'
5
+
6
+ export const getAppConfig = (ctx: IPluginContext): AppConfig => {
7
+ const sourceRoot = ctx.initialConfig.sourceRoot || 'src'
8
+
9
+ const appConfigPath = resolveMainFilePath(path.resolve(ctx.paths.appPath, sourceRoot, 'app.config'))
10
+
11
+ return readConfig(appConfigPath, ctx.initialConfig)
12
+ }
13
+
14
+ export const getAllPages = (appConfig: AppConfig): string[] => {
15
+ const subPackages = [
16
+ ...(appConfig.subPackages ?? []),
17
+ ...(appConfig.subpackages ?? []),
18
+ ]
19
+
20
+ const subPackagePages = subPackages.flatMap(({root, pages}) =>
21
+ pages.map(page => `${root.replace(/\/+$/, '')}/${page.replace(/^\/+/, '')}`)
22
+ )
23
+
24
+ return [...(appConfig.pages ?? []), ...subPackagePages]
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2015",
4
+ "module": "commonjs",
5
+ "strict": true,
6
+ "sourceMap": true,
7
+ "declaration": true,
8
+ "esModuleInterop": true,
9
+ "moduleResolution": "node",
10
+ // 声明文件生成到的文件目录
11
+ "outDir": "./dist/",
12
+ "skipLibCheck": true,
13
+ /* Skip type checking of declaration files. */
14
+ "forceConsistentCasingInFileNames": true,
15
+ "jsx": "react",
16
+ "lib": ["esnext", "dom"],
17
+ "types": ["@types/node"],
18
+ },
19
+ "include": ["src"],
20
+
21
+ }