1 |
|
2 |
|
3 |
|
4 |
|
5 | const path = require('path')
|
6 | const fs = require('fs')
|
7 | const builtinList = require('module').builtinModules
|
8 | const fsExtra = require('fs-extra')
|
9 | const webpack = require('webpack')
|
10 | const { colorconsole, KnownError, getProjectDslName } = require('@hap-toolkit/shared-utils')
|
11 | const globalConfig = require('@hap-toolkit/shared-utils/config')
|
12 | const { ENTRY_TYPE, loaderWrapper } = require('@hap-toolkit/packager/lib/common/utils')
|
13 | const { name, resolveFile } = require('@hap-toolkit/packager/lib/common/info')
|
14 |
|
15 | const packagerPostPath = require.resolve('@hap-toolkit/packager/lib/webpack.post.js')
|
16 | const xvmPostPath = require.resolve(`@hap-toolkit/dsl-xvm/lib/webpack.post.js`)
|
17 | const vuePostPath = require.resolve(`@hap-toolkit/dsl-vue/lib/webpack.post.js`)
|
18 |
|
19 | const pathMap = {
|
20 | packager: packagerPostPath,
|
21 | xvm: xvmPostPath,
|
22 | vue: vuePostPath
|
23 | }
|
24 |
|
25 |
|
26 | const MAIN_PKG_NAME = 'base'
|
27 |
|
28 | const RPKS_SUPPORT_VERSION_FROM = 1040
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 | function getJson(pathJson) {
|
35 | let config
|
36 | if (fs.existsSync(pathJson)) {
|
37 | config = JSON.parse(fs.readFileSync(pathJson))
|
38 | }
|
39 | return config || {}
|
40 | }
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | function resolveEntries(manifest, basedir, cwd) {
|
53 | if (!manifest.router) {
|
54 | throw Error('manifest.json 中未配置路由!')
|
55 | }
|
56 | const entries = {}
|
57 | const pagesConf = manifest.router.pages || {}
|
58 | const widgetsConf = manifest.router.widgets || {}
|
59 | const confsList = [
|
60 | {
|
61 | confs: widgetsConf,
|
62 | type: ENTRY_TYPE.CARD
|
63 | }
|
64 | ]
|
65 | confsList.unshift({
|
66 | confs: pagesConf,
|
67 | type: ENTRY_TYPE.PAGE
|
68 | })
|
69 | const appFile = resolveFile(path.join(basedir, 'app'))
|
70 | if (!appFile) {
|
71 | colorconsole.error('app 文件不存在')
|
72 | process.exit(1)
|
73 | }
|
74 | entries['app'] = './' + path.relative(cwd, appFile) + `?uxType=${ENTRY_TYPE.APP}`
|
75 | confsList.forEach(({ confs, type }) => {
|
76 | Object.keys(confs).forEach(routePath => {
|
77 | const conf = confs[routePath]
|
78 | const entryKey = path.join(routePath, conf.component)
|
79 | const filepath = resolveFile(path.join(basedir, entryKey))
|
80 |
|
81 | if (!filepath) {
|
82 | colorconsole.throw(`编译失败:请确认manifest.json中配置的文件路径存在:${entryKey}`)
|
83 | }
|
84 |
|
85 | let sourceFile = path.relative(cwd, filepath)
|
86 | sourceFile = './' + sourceFile + `?uxType=${type}`
|
87 | sourceFile = sourceFile.replace(/\\/g, '/')
|
88 | entries[entryKey] = sourceFile
|
89 | })
|
90 | })
|
91 | const workers = manifest.workers
|
92 | if (workers && workers.entries && workers.entries instanceof Array) {
|
93 | workers.entries
|
94 | .filter(worker => worker.file)
|
95 | .forEach(worker => {
|
96 | entries[worker.file.replace(/\.js$/, '')] = './src/' + worker.file
|
97 | })
|
98 | }
|
99 | return entries
|
100 | }
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | module.exports = function genWebpackConf(options, mode) {
|
120 |
|
121 | if (options.cwd) {
|
122 | globalConfig.projectPath = options.cwd
|
123 | }
|
124 | const cwd = globalConfig.projectPath
|
125 |
|
126 | const FILE_EXT_LIST = name.extList
|
127 |
|
128 |
|
129 | const SRC_DIR = path.join(cwd, globalConfig.sourceRoot)
|
130 |
|
131 | const SIGN_FOLDER = globalConfig.signRoot
|
132 |
|
133 | const BUILD_DIR = path.join(cwd, globalConfig.outputPath)
|
134 |
|
135 | const DIST_DIR = path.join(cwd, globalConfig.releasePath)
|
136 |
|
137 | const manifestFile = path.join(SRC_DIR, 'manifest.json')
|
138 |
|
139 | const pathPackageJson = path.join(cwd, 'package.json')
|
140 | const packageJson = getJson(pathPackageJson)
|
141 |
|
142 |
|
143 | validateProject()
|
144 |
|
145 |
|
146 | cleanup()
|
147 |
|
148 | let manifest
|
149 | try {
|
150 | manifest = getJson(manifestFile)
|
151 | } catch (e) {
|
152 | throw new KnownError('manifest.json 解析失败!')
|
153 | }
|
154 | validateManifest(manifest, options)
|
155 |
|
156 |
|
157 | setAdaptForV8Version(options.disableScriptV8V65)
|
158 |
|
159 |
|
160 | const entries = resolveEntries(manifest, SRC_DIR, cwd)
|
161 |
|
162 |
|
163 | const env = {
|
164 |
|
165 | NODE_PLATFORM: process.env.NODE_PLATFORM,
|
166 |
|
167 | NODE_PHASE: process.env.NODE_PHASE,
|
168 |
|
169 | NODE_TEST: process.env.NODE_TEST
|
170 | }
|
171 | colorconsole.info(`配置环境:${JSON.stringify(env)}`)
|
172 |
|
173 | const webpackConf = {
|
174 | context: cwd,
|
175 | mode,
|
176 | entry: entries,
|
177 | output: {
|
178 | path: BUILD_DIR,
|
179 | filename: '[name].js'
|
180 | },
|
181 | module: {
|
182 | rules: []
|
183 | },
|
184 | externals: [checkBuiltinModules],
|
185 | plugins: [
|
186 |
|
187 | new webpack.DefinePlugin({
|
188 |
|
189 | ENV_PLATFORM: JSON.stringify(env.NODE_PLATFORM),
|
190 |
|
191 | ENV_PHASE: JSON.stringify(env.NODE_PHASE),
|
192 | ENV_PHASE_DV: env.NODE_PHASE === 'dev',
|
193 | ENV_PHASE_QA: env.NODE_PHASE === 'test',
|
194 | ENV_PHASE_OL: env.NODE_PHASE === 'prod'
|
195 | }),
|
196 |
|
197 | function BuildTimePlugin() {
|
198 | this.hooks.done.tapAsync('end', function(stats, callback) {
|
199 | if (!stats.compilation.errors.length) {
|
200 | const secs = (stats.endTime - stats.startTime) / 1000
|
201 | colorconsole.info(`Build Time Cost: ${secs}s`)
|
202 | }
|
203 | callback()
|
204 | })
|
205 | }
|
206 | ],
|
207 | node: {
|
208 | global: false
|
209 | },
|
210 | resolve: {
|
211 | modules: ['node_modules'],
|
212 | extensions: ['.webpack.js', '.web.js', '.js', '.json'].concat(FILE_EXT_LIST)
|
213 | },
|
214 | stats: {
|
215 | builtAt: false,
|
216 | entrypoints: false,
|
217 | children: false,
|
218 | chunks: false,
|
219 | chunkModules: false,
|
220 | chunkOrigins: false,
|
221 | modules: false,
|
222 | version: false,
|
223 | assets: false
|
224 | }
|
225 | }
|
226 |
|
227 |
|
228 | loadWebpackConfList()
|
229 |
|
230 |
|
231 | webpackConf.devtool = getDevtoolValue(webpackConf.mode, options.devtool)
|
232 |
|
233 | |
234 |
|
235 |
|
236 | function loadWebpackConfList() {
|
237 | const moduleList = [
|
238 | {
|
239 | name: 'packager',
|
240 | path: pathMap['packager']
|
241 | }
|
242 | ]
|
243 |
|
244 | const dslName = getProjectDslName(cwd)
|
245 |
|
246 | moduleList.push({
|
247 | name: `${dslName}-post`,
|
248 | path: pathMap[dslName]
|
249 | })
|
250 |
|
251 | const { package: appPackageName, versionCode, subpackages, workers } = manifest
|
252 | for (let i = 0, len = moduleList.length; i < len; i++) {
|
253 | const fileConf = moduleList[i].path
|
254 | if (fs.existsSync(fileConf)) {
|
255 | try {
|
256 | const moduleWebpackConf = require(fileConf)
|
257 | if (moduleWebpackConf.postHook) {
|
258 | moduleWebpackConf.postHook(
|
259 | webpackConf,
|
260 | {
|
261 | appPackageName,
|
262 | versionCode,
|
263 | nodeConf: env,
|
264 | pathDist: DIST_DIR,
|
265 | pathSrc: SRC_DIR,
|
266 | subpackages,
|
267 | pathBuild: BUILD_DIR,
|
268 | pathSignFolder: SIGN_FOLDER,
|
269 | workers,
|
270 | cwd
|
271 | },
|
272 | {
|
273 | ...options,
|
274 | loaderWrapper: loaderWrapper.bind(null, SRC_DIR)
|
275 | }
|
276 | )
|
277 | }
|
278 | } catch (err) {
|
279 | console.error(`加载webpack配置文件[${fileConf}]出错:${err.message}`, err)
|
280 | }
|
281 | }
|
282 | }
|
283 | }
|
284 |
|
285 | |
286 |
|
287 |
|
288 | function validateProject() {
|
289 | if (!fs.existsSync(manifestFile)) {
|
290 | colorconsole.throw(
|
291 | `请确认项目%projectDir%/${globalConfig.sourceRoot}/下存在manifest.json文件:${manifestFile}`
|
292 | )
|
293 | throw new KnownError(`找不到 ${globalConfig.sourceRoot}/manifest.json`)
|
294 | }
|
295 | }
|
296 |
|
297 | |
298 |
|
299 |
|
300 | function cleanup() {
|
301 | fsExtra.emptyDirSync(BUILD_DIR)
|
302 |
|
303 |
|
304 | if (fs.existsSync(DIST_DIR)) {
|
305 | const zipfiles = fs.readdirSync(DIST_DIR)
|
306 | zipfiles.forEach(function(file) {
|
307 | const curPath = DIST_DIR + '/' + file
|
308 | if (fs.statSync(curPath).isFile()) {
|
309 | fs.unlinkSync(curPath)
|
310 | }
|
311 | })
|
312 | }
|
313 | }
|
314 |
|
315 | |
316 |
|
317 |
|
318 |
|
319 | function setAdaptForV8Version(disableScriptV8V65) {
|
320 | const minPlatformVersion = parseInt(manifest.minPlatformVersion)
|
321 | if (fs.existsSync(pathPackageJson)) {
|
322 | if (!disableScriptV8V65 && minPlatformVersion >= 1040) {
|
323 | const hasDefinedChrome65 =
|
324 | packageJson.browserslist && packageJson.browserslist.includes('chrome 65')
|
325 | colorconsole.log(
|
326 | `当前minPlatformVersion >= 1040,平台采用v8版本为6.5+(对应chrome版本为65版+),工具将不再对V8 6.5版本支持的ES6代码进行转换`
|
327 | )
|
328 | if (hasDefinedChrome65) return
|
329 |
|
330 | packageJson.browserslist = ['chrome 65']
|
331 | fs.writeFileSync(pathPackageJson, JSON.stringify(packageJson, null, 2))
|
332 | } else if (packageJson.browserslist) {
|
333 | delete packageJson.browserslist
|
334 | fs.writeFileSync(pathPackageJson, JSON.stringify(packageJson, null, 2))
|
335 | }
|
336 | }
|
337 | }
|
338 |
|
339 | |
340 |
|
341 |
|
342 |
|
343 | function validateManifest(manifest, options) {
|
344 | const { subpackages } = manifest
|
345 |
|
346 | if (!options.disableSubpackages && subpackages && subpackages.length > 0) {
|
347 | validateManifestSubpackages(subpackages)
|
348 | }
|
349 | }
|
350 |
|
351 | |
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | function validateManifestSubpackages(subpackages) {
|
361 |
|
362 | const nameReg = /^\w+$/
|
363 |
|
364 | const resourceReg = /^\w[\w-/]*$/
|
365 |
|
366 | const nameList = []
|
367 |
|
368 | const resList = []
|
369 | let name = ''
|
370 | let resource = ''
|
371 |
|
372 |
|
373 | let resPath = ''
|
374 | let index = 0
|
375 |
|
376 | |
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 | function checkPathInclusion(resource, currentIndex) {
|
384 | for (let i = 0, l = resList.length; i < l; i++) {
|
385 | const _res = resList[i]
|
386 | if (resource.startsWith(_res) || _res.startsWith(resource)) {
|
387 | colorconsole.throw(
|
388 | `第${currentIndex}分包的资源'${resource}'与第${i +
|
389 | 1}分包的资源'${_res}'有包含关系,请修改`
|
390 | )
|
391 | return true
|
392 | }
|
393 | }
|
394 | return false
|
395 | }
|
396 |
|
397 | subpackages.forEach((subpkg, i) => {
|
398 | name = subpkg.name
|
399 | resource = subpkg.resource
|
400 | resPath = resource && path.join(SRC_DIR, resource)
|
401 | index = i + 1
|
402 | if (!name) {
|
403 | colorconsole.throw(`第${index}分包的名字不能为空,请添加`)
|
404 | } else if (!nameReg.test(name)) {
|
405 | colorconsole.throw(`第${index}分包的名字'${name}'不合法,只能是数字字母下划线组成,请修改`)
|
406 | } else if (name === MAIN_PKG_NAME) {
|
407 | colorconsole.throw(`第${index}分包的名字'${name}'是主包保留名,请修改`)
|
408 | } else if (nameList.indexOf(name) > -1) {
|
409 | colorconsole.throw(`第${index}分包的名字'${name}'已存在,请修改`)
|
410 | } else {
|
411 | nameList.push(name)
|
412 | }
|
413 |
|
414 | if (!resource) {
|
415 | colorconsole.throw(`第${index}分包的资源名不能为空,请添加`)
|
416 | } else if (!resourceReg.test(resource)) {
|
417 | colorconsole.throw(
|
418 | `第${index}分包的资源名'${resource}'不合法,只能是 数字字母_ 开头,数字字母_-/ 组成,请修改`
|
419 | )
|
420 | } else if (resList.indexOf(resource) > -1) {
|
421 | colorconsole.throw(`第${index}分包的资源'${resource}'已被使用,请修改`)
|
422 | } else if (!fs.existsSync(resPath)) {
|
423 | colorconsole.throw(`第${index}分包的资源'${resource}', 文件目录'${resPath}'不存在,请修改`)
|
424 | } else if (!checkPathInclusion(resource, index)) {
|
425 | resList.push(resource)
|
426 | }
|
427 | })
|
428 | colorconsole.warn(
|
429 | `项目已配置分包,若想使用分包功能,请确保平台版本 >= ${RPKS_SUPPORT_VERSION_FROM}`
|
430 | )
|
431 | }
|
432 |
|
433 | |
434 |
|
435 |
|
436 | function checkBuiltinModules(context, request, callback) {
|
437 |
|
438 | let projectDependencies = []
|
439 | if (packageJson.devDependencies) {
|
440 | projectDependencies = Object.keys(packageJson.devDependencies)
|
441 | }
|
442 | if (packageJson.dependencies) {
|
443 | projectDependencies = projectDependencies.concat(Object.keys(packageJson.dependencies))
|
444 | }
|
445 |
|
446 |
|
447 | const enumList = [
|
448 | 'assert',
|
449 | 'console',
|
450 | 'buffer',
|
451 | 'child_process',
|
452 | 'cluster',
|
453 | 'console',
|
454 | 'constants',
|
455 | 'crypto',
|
456 | 'dgram',
|
457 | 'dns',
|
458 | 'domain',
|
459 | 'events',
|
460 | 'fs',
|
461 | 'http',
|
462 | 'https',
|
463 | 'module',
|
464 | 'net',
|
465 | 'os',
|
466 | 'path',
|
467 | 'process',
|
468 | 'punycode',
|
469 | 'querystring',
|
470 | 'readline',
|
471 | 'repl',
|
472 | 'stream',
|
473 | 'string_decoder',
|
474 | 'sys',
|
475 | 'timers',
|
476 | 'tls',
|
477 | 'tty',
|
478 | 'url',
|
479 | 'util',
|
480 | 'vm',
|
481 | 'zlib'
|
482 | ]
|
483 | const externalsList = Array.isArray(builtinList) ? builtinList : enumList
|
484 |
|
485 | if (externalsList.indexOf(request) > -1 && projectDependencies.indexOf(request) === -1) {
|
486 | colorconsole.warn(
|
487 | `您当前使用了 ${request} 似乎是 node 原生模块, 快应用不是 node 环境不支持 node 原生模块`
|
488 | )
|
489 | }
|
490 | callback()
|
491 | }
|
492 |
|
493 | |
494 |
|
495 |
|
496 |
|
497 |
|
498 | function getDevtoolValue(mode, devtool) {
|
499 | const sourcemaps = {
|
500 | development: {
|
501 | default: 'cheap-module-eval-source-map',
|
502 | options: [
|
503 | 'none',
|
504 | 'eval',
|
505 | 'cheap-eval-source-map',
|
506 | 'cheap-module-eval-source-map',
|
507 | 'eval-source-map',
|
508 | 'cheap-source-map',
|
509 | 'cheap-module-source-map',
|
510 | 'inline-cheap-source-map',
|
511 | 'inline-cheap-module-source-map',
|
512 | 'source-map',
|
513 | 'inline-source-map',
|
514 | 'hidden-source-map',
|
515 | 'nosources-source-map'
|
516 | ]
|
517 | },
|
518 | production: {
|
519 | default: 'none',
|
520 | options: [
|
521 | 'none',
|
522 | 'cheap-source-map',
|
523 | 'cheap-module-source-map',
|
524 | 'source-map',
|
525 | 'hidden-source-map',
|
526 | 'nosources-source-map'
|
527 | ]
|
528 | }
|
529 | }
|
530 | const sourcemapArr = sourcemaps[mode].options
|
531 | const defaultSourcemap = sourcemaps[mode].default
|
532 | if (typeof devtool !== 'string') {
|
533 | return defaultSourcemap
|
534 | }
|
535 | if (sourcemapArr.indexOf(devtool) === -1) {
|
536 | colorconsole.warn(`${mode} 模式 devtool 不支持 '${devtool}', 改为默认 '${defaultSourcemap}'`)
|
537 | return defaultSourcemap
|
538 | }
|
539 | return devtool
|
540 | }
|
541 | return webpackConf
|
542 | }
|