diff --git a/packages/hap-packager/src/common/info.js b/packages/hap-packager/src/common/info.js index f4d44a6f..b3576a65 100644 --- a/packages/hap-packager/src/common/info.js +++ b/packages/hap-packager/src/common/info.js @@ -34,7 +34,8 @@ export function resolveFile(scriptFilePath) { * @return {array} */ export function getEntryFiles(entry) { - const entryFiles = Object.keys(entry || {}).map((file) => { + const normalizedEntry = getNormalizedEntry(entry) + const entryFiles = Object.keys(normalizedEntry).map((file) => { return file + '.js' }) return entryFiles @@ -47,9 +48,10 @@ export function getEntryFiles(entry) { */ export function getLiteEntryFiles(entry) { const liteEntry = [] - Object.keys(entry || {}).forEach((file) => { - const fileInfo = entry[file] - const importStr = fileInfo.import[0] || '' + const normalizedEntry = getNormalizedEntry(entry) + Object.keys(normalizedEntry).forEach((file) => { + const fileInfo = normalizedEntry[file] + const importStr = (fileInfo && fileInfo.import && fileInfo.import[0]) || '' if (importStr.indexOf('?') >= 0) { const paramStr = importStr.split('?')[1] const paramArr = paramStr.split('&') @@ -61,6 +63,19 @@ export function getLiteEntryFiles(entry) { return liteEntry } +/** + * 获取当前生效的 webpack entry 配置。 + * 支持 watch 模式下通过 entry 函数动态刷新入口。 + * @param {object|function} entry + * @return {object} + */ +export function getNormalizedEntry(entry) { + if (typeof entry === 'function') { + return entry() || {} + } + return entry || {} +} + /** * 获取骨架屏配置信息 * @param {String} src - 项目src路径 diff --git a/packages/hap-packager/src/plugins/handler-plugin.js b/packages/hap-packager/src/plugins/handler-plugin.js index 1893455c..c924b06e 100644 --- a/packages/hap-packager/src/plugins/handler-plugin.js +++ b/packages/hap-packager/src/plugins/handler-plugin.js @@ -5,7 +5,7 @@ import Compilation from 'webpack/lib/Compilation' import { globalConfig, compileOptionsObject, compileOptionsMeta } from '@hap-toolkit/shared-utils' -import { getEntryFiles, getLiteEntryFiles } from '../common/info' +import { getEntryFiles, getLiteEntryFiles, getNormalizedEntry } from '../common/info' let ConcatSource @@ -22,6 +22,7 @@ HandlerPlugin.prototype.apply = function (compiler) { ConcatSource = compiler.webpack.sources.ConcatSource const workersPath = this.options.workers const enableE2e = this.options.enableE2e + const entryState = this.options.entryState compiler.hooks.compilation.tap('HandlerPlugin', function (compilation) { compilation.hooks.processAssets.tap( { @@ -30,8 +31,11 @@ HandlerPlugin.prototype.apply = function (compiler) { }, () => { // 如果进行抽取公共js则需通过入口文件来判断是不是抽取出的Chunks - const entryFiles = getEntryFiles(compiler.options.entry) - const liteEntryFiles = getLiteEntryFiles(compiler.options.entry) + const currentEntry = entryState + ? entryState.current + : getNormalizedEntry(compiler.options.entry) + const entryFiles = getEntryFiles(currentEntry) + const liteEntryFiles = getLiteEntryFiles(currentEntry) const { originType } = compileOptionsObject || {} const isDevMode = globalConfig.mode === 'development' compilation.chunks.forEach(function (chunk) { diff --git a/packages/hap-packager/src/plugins/resource-plugin.js b/packages/hap-packager/src/plugins/resource-plugin.js index b14ba660..83848530 100644 --- a/packages/hap-packager/src/plugins/resource-plugin.js +++ b/packages/hap-packager/src/plugins/resource-plugin.js @@ -18,7 +18,7 @@ import { globalConfig } from '@hap-toolkit/shared-utils' -import { name } from '../common/info' +import { name, getNormalizedEntry } from '../common/info' import { updateManifest } from '../common/shared' const { PACKAGER_BUILD_DONE } = eventBus @@ -221,10 +221,13 @@ ResourcePlugin.prototype.apply = function (compiler) { const webpackOptions = compiler.options // 监听时处理 compiler.hooks.watchRun.tapAsync('ResourcePlugin', function (watching, callback) { - Object.keys(webpackOptions.entry).forEach(function (key) { + const currentEntry = options.entryState + ? options.entryState.current + : getNormalizedEntry(webpackOptions.entry) + Object.keys(currentEntry).forEach(function (key) { // 重置 changedJS globalConfig.changedJS = {} - const val = webpackOptions.entry[key] + const val = currentEntry[key] if (val instanceof Array && !/app\.js/.test(key)) { // 删除webpack-dev-server注入的watch依赖 val[0].indexOf('webpack-dev-server') !== -1 && val.shift() diff --git a/packages/hap-packager/src/plugins/splitchunks-adapt-plugin.js b/packages/hap-packager/src/plugins/splitchunks-adapt-plugin.js index 1eb97643..f47edb86 100644 --- a/packages/hap-packager/src/plugins/splitchunks-adapt-plugin.js +++ b/packages/hap-packager/src/plugins/splitchunks-adapt-plugin.js @@ -5,7 +5,7 @@ import path from 'path' import Compilation from 'webpack/lib/Compilation' -import { getEntryFiles } from '../common/info' +import { getEntryFiles, getNormalizedEntry } from '../common/info' import { compileOptionsMeta } from '@hap-toolkit/shared-utils' import { isEmptyObject } from '@hap-toolkit/compiler' @@ -106,7 +106,10 @@ class SplitChunksAdaptPlugin { // 这个钩子负责生成chunkFileMapStr,兼容release包里找不到文件路径,因为压缩后会把文件名打为数字id compilation.hooks.optimizeChunkIds.tap(pluginName, (chunks) => { - entryFiles = getEntryFiles(compiler.options.entry) + const currentEntry = options.entryState + ? options.entryState.current + : getNormalizedEntry(compiler.options.entry) + entryFiles = getEntryFiles(currentEntry) const chunksMap = Array.from(chunks) .filter((chunk) => { diff --git a/packages/hap-packager/src/post-handler/lite-card-post.js b/packages/hap-packager/src/post-handler/lite-card-post.js index 959da6b1..8823a029 100644 --- a/packages/hap-packager/src/post-handler/lite-card-post.js +++ b/packages/hap-packager/src/post-handler/lite-card-post.js @@ -63,14 +63,6 @@ function markUrl(actions) { } } else if (Array.isArray(actions.url)) { let hasBinding = false - const rawUrlList = actions.url.map((url) => { - if (isExpr(url)) { - hasBinding = true - let { rawExpr } = getPrefixExpr(url) - return `{{${rawExpr}}}` - } - return url - }) const prefixUrlList = actions.url.map((url) => { if (isExpr(url)) { diff --git a/packages/hap-packager/src/webpack.post.js b/packages/hap-packager/src/webpack.post.js index 5678dafa..702c2583 100644 --- a/packages/hap-packager/src/webpack.post.js +++ b/packages/hap-packager/src/webpack.post.js @@ -47,6 +47,7 @@ function postHook(webpackConf, defaultsOptions, quickappConfig = {}) { pathSrc, subpackages, workers, + entryState, originType, useTreeShaking } = defaultsOptions @@ -119,6 +120,7 @@ function postHook(webpackConf, defaultsOptions, quickappConfig = {}) { if (globalConfig.isSmartMode) { webpackConf.plugins.push( new SplitChunksAdaptPlugin({ + entryState, subpackages, disableSubpackages: compileOptionsObject.disableSubpackages }) @@ -133,6 +135,7 @@ function postHook(webpackConf, defaultsOptions, quickappConfig = {}) { new HandlerPlugin({ pathSrc: pathSrc, workers: workers, + entryState, enableE2e: compileOptionsObject.enableE2e, useTreeShaking }), @@ -143,6 +146,7 @@ function postHook(webpackConf, defaultsOptions, quickappConfig = {}) { new ResourcePlugin({ src: pathSrc, dest: pathBuild, + entryState, comment: rpkComment, projectRoot: globalConfig.projectPath, configDebugInManifest, diff --git a/packages/hap-toolkit/src/gen-webpack-conf/index.js b/packages/hap-toolkit/src/gen-webpack-conf/index.js index a31aa7a0..3dfbfe1e 100644 --- a/packages/hap-toolkit/src/gen-webpack-conf/index.js +++ b/packages/hap-toolkit/src/gen-webpack-conf/index.js @@ -162,6 +162,7 @@ export default async function genWebpackConf(launchOptions, mode) { // 页面文件 const entries = resolveEntries(manifest, SRC_DIR, cwd) + const entryState = { current: entries } // 环境变量 const env = { @@ -228,7 +229,7 @@ export default async function genWebpackConf(launchOptions, mode) { context: cwd, mode, cache, - entry: entries, + entry: () => entryState.current, output: { globalObject: 'window', path: BUILD_DIR, @@ -293,7 +294,9 @@ export default async function genWebpackConf(launchOptions, mode) { }, new ManifestWatchPlugin({ appRoot: cwd, - root: SRC_DIR + root: SRC_DIR, + buildDir: BUILD_DIR, + entryState }) ], resolve: { @@ -503,6 +506,7 @@ export default async function genWebpackConf(launchOptions, mode) { useTreeShaking: quickappConfig && quickappConfig.useTreeShaking ? !!quickappConfig.useTreeShaking : false, workers, + entryState, cwd, originType: compileOptionsObject.originType, ideConfig: launchOptions.ideConfig diff --git a/packages/hap-toolkit/src/plugins/manifest-watch-plugin.js b/packages/hap-toolkit/src/plugins/manifest-watch-plugin.js index c0563315..6b4418da 100644 --- a/packages/hap-toolkit/src/plugins/manifest-watch-plugin.js +++ b/packages/hap-toolkit/src/plugins/manifest-watch-plugin.js @@ -4,6 +4,7 @@ */ import path from 'path' +import fs from 'fs' import { colorconsole, readJson, logger, eventBus } from '@hap-toolkit/shared-utils' import { resolveEntries } from '../utils' @@ -21,6 +22,8 @@ export default class ManifestWatchPlugin { constructor(options) { this.appRoot = options.appRoot this.root = options.root + this.buildDir = options.buildDir + this.entryState = options.entryState this.manifestFile = path.resolve(this.root, 'manifest.json') let entries = {} try { @@ -31,32 +34,81 @@ export default class ManifestWatchPlugin { this.list = Object.keys(entries) this.list = sort(this.list) } - hasChanged(newList) { - const sorted = sort(newList) - const changed = JSON.stringify(sorted) !== JSON.stringify(this.list) + + getRemovedEntries(newList) { + const newSet = new Set(newList) + return this.list.filter((key) => !newSet.has(key)) + } + + updateEntries(entries) { + const newList = sort(Object.keys(entries)) + const removedEntries = this.getRemovedEntries(newList) + const changed = JSON.stringify(newList) !== JSON.stringify(this.list) if (changed) { - this.list = sorted + this.list = newList + this.entryState && (this.entryState.current = entries) + } + return { + changed, + removedEntries } - return changed } + + removeBuildArtifacts(entryKeys) { + if (!this.buildDir || !entryKeys || entryKeys.length === 0) { + return + } + entryKeys.forEach((entryKey) => { + const entryDir = path.dirname(entryKey) + if (entryDir && entryDir !== '.') { + const targetDir = path.join(this.buildDir, entryDir) + if (fs.existsSync(targetDir)) { + fs.rmSync(targetDir, { recursive: true, force: true }) + this.removeEmptyParentDirs(path.dirname(targetDir)) + } + return + } + + ;[ + `${entryKey}.js`, + `${entryKey}.js.map`, + `${entryKey}.css.json`, + `${entryKey}.template.json` + ].forEach((relativeFile) => { + const targetFile = path.join(this.buildDir, relativeFile) + if (!fs.existsSync(targetFile)) { + return + } + fs.unlinkSync(targetFile) + this.removeEmptyParentDirs(path.dirname(targetFile)) + }) + }) + } + + removeEmptyParentDirs(dir) { + while (dir && dir.startsWith(this.buildDir) && dir !== this.buildDir) { + if (!fs.existsSync(dir) || fs.readdirSync(dir).length > 0) { + return + } + fs.rmdirSync(dir) + dir = path.dirname(dir) + } + } + apply(compiler) { compiler.hooks.watchRun.tapAsync('watch', (compiler, callback) => { eventBus.emit(PACKAGER_WATCH_START) logger.clear() try { const modifiedFiles = compiler.modifiedFiles - // 当发生变化的文件是 app.json,且 list 列表有增/删时,更新入口文件 - // TODO 页面减少时不会移除 entry - // https://stackoverflow.com/a/39401288/1087831 + // 当发生变化的文件是 manifest.json,且入口列表有增删时,更新当前编译入口 if (modifiedFiles && modifiedFiles.has(this.manifestFile)) { /** @readonly */ const manifest = readJson(this.manifestFile) const entries = resolveEntries(manifest, this.root, this.appRoot) - const newList = Object.keys(entries) - if (this.hasChanged(newList)) { - // 增删页面要修改 webpack entries - this.list = newList - compiler.options.entry = entries + const { changed, removedEntries } = this.updateEntries(entries) + if (changed) { + this.removeBuildArtifacts(removedEntries) } } } catch (err) {