UNPKG

10.6 kBJavaScriptView Raw
1const fs = require('fs-extra')
2const path = require('path')
3
4const validatePathname = require('./validate-pathname')
5const validateConfigDist = require('./validate-config-dist')
6// const validateAppType = require('../utils/get-app-type')
7const getCwd = require('../utils/get-cwd')
8const readBuildConfigFile = require('../utils/read-build-config-file')
9// const getPathnameBuildConfigFile = require('../utils/get-pathname-build-config-file')
10const {
11 keyFileProjectConfigTempFull,
12 keyFileProjectConfigTempPortion,
13 filenameProjectConfigTempFull,
14 filenameProjectConfigTempPortion,
15 propertiesToExtract: _propertiesToExtract,
16 dirConfigTemp: _dirConfigTemp,
17 typesSPA,
18} = require('../defaults/before-build')
19
20/**
21 * 根据 koot.config.js 生成 koot.js 和打包配置对象
22 *
23 * 如果项目采用 0.6 之后的配置方式 (使用 koot.config.js,其中有全部配置项),以下内容会写入环境变量
24 * - KOOT_PROJECT_CONFIG_FULL_PATHNAME - 项目配置文件 (临时文件)
25 *
26 * 项目配置:在 0.6 之前为 koot.js,0.6 之后为自动生成的临时配置文件
27 * - 使用临时配置文件是为了兼容 0.6 之前的行为
28 * - TODO: 在未来可能会抛弃独立配置文件行为,界时该方法会改写
29 *
30 * @async
31 * @returns {Object} 打包配置对象
32 */
33module.exports = async (projectDir = getCwd()) => {
34
35 // const ENV = process.env.WEBPACK_BUILD_ENV
36
37 /** @type {String} 完整配置文件路径名 */
38 let fileFullConfig = typeof process.env.KOOT_BUILD_CONFIG_PATHNAME === 'string'
39 ? process.env.KOOT_BUILD_CONFIG_PATHNAME
40 : path.resolve(projectDir, 'koot.config.js')
41
42 // 如果完整配置文件不存在,转为旧模式 (koot.js + koot.build.js)
43 if (!fs.existsSync(fileFullConfig)) {
44 const buildConfig = await readBuildConfigFile()
45 return validateBuildConfig(buildConfig)
46 }
47
48 /** @type {Object} 完整配置 */
49 const fullConfig = { ...require(fileFullConfig) }
50
51 /** @type {Boolean} 是否定制了项目配置文件路径名 */
52 const isCustomProjectConfig = typeof process.env.KOOT_PROJECT_CONFIG_FULL_PATHNAME === 'string'
53
54 /** @type {Array} 需要抽取到项目配置中的项 */
55 const propertiesToExtract = [..._propertiesToExtract]
56
57 // 目标文件是否是完整配置文件
58 const isFullConfig = propertiesToExtract.some(([key]) => typeof fullConfig[key] !== 'undefined')
59
60 // console.log({
61 // fileFullConfig,
62 // isCustomProjectConfig,
63 // fullConfig,
64 // })
65
66 if (isFullConfig) {
67
68 const dirConfigTemp = path.resolve(projectDir, _dirConfigTemp)
69 await fs.ensureDir(dirConfigTemp)
70
71 /** @type {Boolean} 当前项目是否是 SPA */
72 const isSPA = typesSPA.includes(fullConfig.type)
73
74 if (isSPA) {
75 // SPA 项目: 添加顶层项 inject
76 propertiesToExtract.push(['inject', undefined])
77
78 // SPA 项目: 如果配置顶层没有 inject 同时 server.inject 存在值,将 server.inject 移至顶层
79 if (!fullConfig.inject &&
80 typeof fullConfig.server === 'object' &&
81 typeof fullConfig.server.inject !== 'undefined'
82 ) {
83 fullConfig.inject = fullConfig.server.inject
84 }
85 } else {
86 // 同构项目: 如果配置顶层有 inject 同时 server.inject 没有值,将 inject 移至顶层 server.inject
87 if (!!fullConfig.inject &&
88 typeof fullConfig.server === 'object' &&
89 !fullConfig.server.inject
90 ) {
91 fullConfig.inject.server = fullConfig.inject
92 delete fullConfig.inject
93 }
94 }
95
96 // 项目配置
97 const projectConfigFull = {}
98 const projectConfigPortion = {}
99 const propertiesPortion = [
100 'redux',
101 'server'
102 ]
103
104 // 将打包配置从完整配置中分离
105 const buildConfig = propertiesToExtract.reduce((configRemains, curr) => {
106 // console.log(configRemains)
107 const [key, defaultValue] = curr
108 const value = configRemains[key] || defaultValue
109 projectConfigFull[key] = typeof value === 'object' ? { ...value } : value
110 if (propertiesPortion.includes(key))
111 projectConfigPortion[key] = typeof value === 'object' ? { ...value } : value
112 delete configRemains[key]
113 return configRemains
114 }, fullConfig)
115
116 delete projectConfigPortion.server.onRender
117
118 // 如果定制了配置文件路径,直接返回结果
119 if (isCustomProjectConfig) {
120 return {
121 ...validateBuildConfig(buildConfig),
122 [keyFileProjectConfigTempFull]: process.env.KOOT_PROJECT_CONFIG_FULL_PATHNAME,
123 [keyFileProjectConfigTempPortion]: process.env.KOOT_PROJECT_CONFIG_PORTION_PATHNAME
124 }
125 }
126
127 // const {
128 // name,
129 // type,
130 // template,
131 // router,
132 // redux = {},
133 // client = {},
134 // server = {},
135 // ...buildConfig
136 // } = require(fileFullConfig)
137
138 // 转换项目配置: 将路径转为 require()
139 const evalValue = (objectName, key) => {
140 try {
141 if (eval(`typeof ${objectName}.${key} === 'string'`)) {
142 const value = eval(`${objectName}.${key}`)
143 const pathname = path.isAbsolute(value)
144 ? value
145 : validatePathname(value, projectDir).replace(/\\/g, '\\\\')
146 const result = path.isAbsolute(pathname)
147 ? pathname
148 : ('../../../' + pathname.replace(/^\.\//, ''))
149 eval(`${objectName}.${key} = \`require('${result}').default\``)
150 }
151 } catch (e) { }
152 }
153 const validateProjectConfig = (keys) => {
154 keys.forEach(key => {
155 evalValue('projectConfigFull', key)
156 evalValue('projectConfigPortion', key)
157 })
158 }
159 validateProjectConfig([
160 'router',
161 'redux.combineReducers',
162 'redux.store',
163 'client.before',
164 'client.after',
165 'client.onRouterUpdate',
166 'client.onHistoryUpdate',
167 'server.reducers',
168 'server.inject',
169 'server.before',
170 'server.after',
171 'server.onRender',
172 'server.onRender.beforeDataToStore',
173 'server.onRender.afterDataToStore',
174 'inject',
175 ])
176
177 // console.log(projectConfigFull)
178 // 生成项目配置文件内容
179 const tempFull = propertiesToExtract.map(([key]) => {
180 let result = ''
181 if (key === 'server') {
182 if (isSPA) return ''
183 result = `export const ${key} = __SERVER__ ? ${JSON.stringify(projectConfigFull[key])} : {};`
184 } else {
185 result = `export const ${key} = ${JSON.stringify(projectConfigFull[key])};`
186 }
187 return result
188 })
189 .join('\n')
190 .replace(/"require\((.+?)\).default"/g, `require($1).default`)
191
192 const tempPortion = propertiesPortion.map((key) => {
193 let result = ''
194 if (key === 'server') {
195 if (isSPA) return ''
196 result = `export const ${key} = __SERVER__ ? ${JSON.stringify(projectConfigPortion[key])} : {};`
197 } else {
198 result = `export const ${key} = ${JSON.stringify(projectConfigPortion[key])};`
199 }
200 return result
201 })
202 .join('\n')
203 .replace(/"require\((.+?)\).default"/g, `require($1).default`)
204
205 // console.log(tempFull)
206
207 // 写入项目配置文件 (临时)
208 const pathFull = path.resolve(dirConfigTemp, filenameProjectConfigTempFull.replace(/\*/g, Date.now()))
209 process.env.KOOT_PROJECT_CONFIG_FULL_PATHNAME = pathFull
210 await fs.writeFile(pathFull, tempFull, 'utf-8')
211
212 const pathPortion = path.resolve(dirConfigTemp, filenameProjectConfigTempPortion.replace(/\*/g, Date.now()))
213 process.env.KOOT_PROJECT_CONFIG_PORTION_PATHNAME = pathPortion
214 await fs.writeFile(pathPortion, tempPortion, 'utf-8')
215
216 return {
217 ...validateBuildConfig(buildConfig),
218 [keyFileProjectConfigTempFull]: pathFull,
219 [keyFileProjectConfigTempPortion]: pathPortion
220 }
221
222 } else {
223 // 非完整配置情况,为兼容旧版本 (< 0.6) 而设
224 const buildConfig = await readBuildConfigFile()
225 return validateBuildConfig(buildConfig)
226 }
227}
228
229// 调整构建配置对象
230const validateBuildConfig = (config = {}) => {
231
232 // 改变配置项: dest -> dist
233 if (typeof config.dest !== 'undefined') {
234 config.dist = config.dest
235 delete config.dest
236 }
237 if (typeof config.dist !== 'undefined') {
238 validateConfigDist(config.dist)
239 }
240
241 // 改变配置项: webpack.config -> config
242 if (typeof config.webpack === 'object') {
243 /**
244 * 将配置中的 webpack 对象内的内容应用到配置对象顶层
245 * @param {String} nameInObject
246 * @param {String} nameAfter
247 */
248 const applyWebpackConfig = (nameInObject, nameAfter) => {
249 if (typeof config.webpack[nameInObject] !== 'undefined') {
250 config[nameAfter] = config.webpack[nameInObject]
251 delete config.webpack[nameInObject]
252 }
253 }
254 applyWebpackConfig('config', 'config')
255 applyWebpackConfig('beforeBuild', 'beforeBuild')
256 applyWebpackConfig('afterBuild', 'afterBuild')
257 applyWebpackConfig('defines', 'defines')
258 applyWebpackConfig('dll', 'webpackDll')
259 applyWebpackConfig('hmr', 'webpackHmr')
260 applyWebpackConfig('compilerHook', 'webpackCompilerHook')
261 Object.keys(config.webpack).forEach(key => {
262 applyWebpackConfig(key, 'webpack' + key.substr(0, 1).toUpperCase() + key.substr(1))
263 })
264 delete config.webpack
265 }
266
267 return config
268}