UNPKG

7.08 kBJavaScriptView Raw
1/*
2 * Copyright (C) 2017, hapjs.org. All rights reserved.
3 */
4
5const path = require('path')
6const fs = require('fs')
7const yargs = require('yargs')
8const fsExtra = require('fs-extra')
9
10const webpack = require('webpack')
11
12const {colorconsole} = require('./packager/lib/utils')
13const info = require('./packager/lib/info')
14// 暂时暴露全局
15global.colorconsole = colorconsole
16
17// 开发者传递的参数
18const options = Object.assign({}, yargs.argv)
19
20// 支持文件扩展名
21const FILE_EXT_LIST = info.name.extList
22// 排除资源文件扩展名
23const FILE_EXT_NORES = FILE_EXT_LIST.concat(['.js', '.jsx', '.coffee', '.ts', '.tsx', '.vue', '.css', '.less', '.sass', '.styl', '.html', '.json', '.md'])
24
25// 项目目录
26const pathProject = process.cwd()
27// 源代码目录
28const pathSrc = path.join(pathProject, 'src')
29// 输出目标目录
30const nameBuild = 'build'
31// 构建目录
32const pathBuild = path.join(pathProject, nameBuild)
33// 最终发布目录
34const pathDist = path.join(pathProject, 'dist')
35// 打包配置文件
36const nameManifest = 'manifest.json'
37const pathManifest = path.join(pathSrc, nameManifest)
38
39// 入口文件
40const nameEntry = 'game.js'
41const pathEntry = path.join(pathSrc, nameEntry)
42
43// 工具目录
44const pathDirname = __dirname
45
46// 校验项目工程
47!validateProject()
48
49// 项目名(如果没有, 则默认为Bundle)
50const appPackageName = getProjectName()
51
52// 资源文件
53const zipReses = {}
54
55// 提取脚本文件,资源文件
56extractSourceFiles('.', false)
57
58// 配置环境
59const nodeConf = parseEnv()
60
61const webpackConf = {
62 entry: {
63 game: pathEntry
64 },
65 output: {
66 path: pathBuild,
67 filename: '[name].js'
68 },
69 module: {
70 rules: []
71 },
72 plugins: [
73 // 定义环境变量
74 new webpack.DefinePlugin({
75 // 平台:na
76 ENV_PLATFORM: JSON.stringify(nodeConf.NODE_PLATFORM),
77 // 阶段: dv|qa|ol
78 ENV_PHASE: JSON.stringify(nodeConf.NODE_PHASE),
79 ENV_PHASE_DV: nodeConf.NODE_PHASE === 'dv',
80 ENV_PHASE_QA: nodeConf.NODE_PHASE === 'qa',
81 ENV_PHASE_OL: nodeConf.NODE_PHASE === 'ol'
82 }),
83 // 编译耗时
84 function () {
85 this.plugin('run', function (compiler, callback) {
86 process.webpackDateS = new Date()
87 callback()
88 })
89 this.plugin('watch-run', function (compiler, callback) {
90 process.webpackDateS = new Date()
91 callback()
92 })
93 this.plugin('done', function () {
94 process.webpackDateE = new Date()
95 const secCost = (process.webpackDateE - process.webpackDateS) / 1000
96 colorconsole.info(`Build Time Cost: ${secCost}s`)
97 })
98 }
99 ],
100 node: {
101 global: false
102 },
103 resolve: {
104 modules: [
105 'node_modules',
106 // 测试用例在test目录下
107 path.join(pathProject, 'test')
108 ],
109 extensions: ['.webpack.js', '.web.js', '.js', '.json'].concat(FILE_EXT_LIST)
110 },
111 stats: {
112 children: false,
113 chunks: false,
114 chunkModules: false,
115 chunkOrigins: false,
116 modules: false,
117 version: false,
118 assets: false
119 }
120}
121
122// 环境配置
123if (nodeConf.NODE_PHASE === 'dv') {
124 // 开发:sourcemap
125 webpackConf.devtool = 'source-map'
126}
127else {
128 // 正式:压缩去重
129 webpackConf.plugins.push(new webpack.optimize.DedupePlugin())
130 webpackConf.plugins.push(new webpack.optimize.UglifyJsPlugin())
131}
132
133// 加载配置
134loadWebpackConfList()
135
136module.exports = webpackConf
137
138/**
139 * 尝试加载每个模块的webpack配置
140 */
141function loadWebpackConfList () {
142 const moduleList = findModuleList(pathDirname)
143
144 // 增加:开发者项目目录下的config文件夹
145 // 注意:Hook机制不保证向后兼容
146 moduleList.push({ name: '', path: path.join(pathProject, 'config') })
147
148 for (let i = 0, len = moduleList.length; i < len; i++) {
149 const moduleItem = moduleList[i]
150 const fileConf = path.join(moduleItem.path, 'webpack.config.js')
151 if (fs.existsSync(fileConf)) {
152 try {
153 const moduleWebpackConf = require(fileConf)
154 if (moduleWebpackConf.postHook) {
155 moduleWebpackConf.postHook(webpackConf, {
156 appPackageName,
157 nodeConf,
158 pathDist,
159 pathDirname,
160 zipReses,
161 pathBuild
162 }, options)
163 }
164 }
165 catch (err) {
166 console.error(`加载webpack配置文件[${fileConf}]出错:${err.message}`)
167 }
168 }
169 }
170}
171
172/**
173 * 查找模块列表
174 * @param parentDir
175 * @return {Array}
176 */
177function findModuleList (parentDir) {
178 const moduleList = []
179 const fileNameList = fs.readdirSync(pathDirname)
180 for (let i = 0, len = fileNameList.length; i < len; i++) {
181 const fileName = fileNameList[i]
182 const filePath = path.join(parentDir, fileName)
183 const fileStat = fs.statSync(filePath)
184 if (fileStat.isDirectory()) {
185 moduleList.push({
186 name: fileName,
187 path: filePath
188 })
189 }
190 }
191 return moduleList
192}
193
194/**
195 * 解析NODE环境的参数
196 */
197function parseEnv(){
198 const config = {
199 // 平台:na
200 NODE_PLATFORM: process.env.NODE_PLATFORM,
201 // 阶段: dv|qa|ol
202 NODE_PHASE: process.env.NODE_PHASE,
203 // 是否注入测试框架
204 NODE_TEST: process.env.NODE_TEST
205 }
206 colorconsole.info(`配置环境:${JSON.stringify(config)}`)
207 return config
208}
209
210/**
211 * 验证项目配置正确
212 */
213function validateProject () {
214 if (!fs.existsSync(pathManifest)) {
215 colorconsole.throw(`请确认项目%projectDir%/src/下存在manifest.json文件:${pathManifest}`)
216 }
217
218 if (!fs.existsSync(pathEntry)) {
219 colorconsole.throw(`请确认项目%projectDir%/src/下存在game.js文件:${pathEntry}`)
220 }
221
222 // 清空build目录
223 fsExtra.emptyDirSync(pathBuild)
224 // 清空dist路径
225 if (fs.existsSync(pathDist)) {
226 const zipfiles = fs.readdirSync(pathDist)
227 zipfiles.forEach(function (file) {
228 const curPath = pathDist + '/' + file
229 if (fs.statSync(curPath).isFile()) {
230 fs.unlinkSync(curPath)
231 }
232 })
233 }
234 return true
235}
236
237
238/**
239 * 获取项目名
240 */
241function getProjectName() {
242 const config = JSON.parse(fs.readFileSync(pathManifest))
243 return (config && config.package) || 'Bundle'
244}
245
246/**
247 * 提取资源文件
248 * @param dir
249 * @param common
250 */
251function extractSourceFiles(dir, common) {
252 dir = dir || '.'
253 var directory = path.join(pathSrc, dir)
254 var name
255 // 递归遍历目录
256 fs.readdirSync(directory)
257 .forEach(function (file) {
258 var fullpath = path.join(directory, file)
259 var stat = fs.statSync(fullpath)
260 var basename = path.basename(fullpath)
261 var extname = path.extname(fullpath)
262 if (stat.isFile() ) {
263 if(basename === nameManifest || FILE_EXT_NORES.indexOf(extname) < 0 ) { // 资源文件
264 // 采用绝对路径
265 name = path.join(nameBuild, dir, path.basename(file))
266 name = name.replace(/\\/g, '/')
267 zipReses[name] = fullpath
268 }
269 }
270 else if (stat.isDirectory()) {
271 var subdir = path.join(dir, file)
272 var iscommon = common || (file.toLowerCase() === 'common')
273 extractSourceFiles(subdir, iscommon)
274 }
275 })
276}