UNPKG

9.72 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 projectConfig = {}
98
99 // 将打包配置从完整配置中分离
100 const buildConfig = propertiesToExtract.reduce((configRemains, curr) => {
101 // console.log(configRemains)
102 const [key, defaultValue] = curr
103 projectConfig[key] = configRemains[key] || defaultValue
104 delete configRemains[key]
105 return configRemains
106 }, fullConfig)
107
108 // 如果定制了配置文件路径,直接返回结果
109 if (isCustomProjectConfig) {
110 return {
111 ...validateBuildConfig(buildConfig),
112 [keyFileProjectConfigTempFull]: process.env.KOOT_PROJECT_CONFIG_FULL_PATHNAME,
113 [keyFileProjectConfigTempPortion]: process.env.KOOT_PROJECT_CONFIG_PORTION_PATHNAME
114 }
115 }
116
117 // const {
118 // name,
119 // type,
120 // template,
121 // router,
122 // redux = {},
123 // client = {},
124 // server = {},
125 // ...buildConfig
126 // } = require(fileFullConfig)
127
128 // 转换项目配置: 将路径转为 require()
129 const validateProjectConfig = (keys) => {
130 keys.forEach(key => {
131 if (eval(`typeof projectConfig.${key} === 'string'`)) {
132 const value = eval(`projectConfig.${key}`)
133 const pathname = path.isAbsolute(value)
134 ? value
135 : validatePathname(value, projectDir).replace(/\\/g, '\\\\')
136 const result = path.isAbsolute(pathname)
137 ? pathname
138 : ('../../../' + pathname.replace(/^\.\//, ''))
139 eval(`projectConfig.${key} = \`require('${result}').default\``)
140 }
141 })
142 }
143 validateProjectConfig([
144 'router',
145 'redux.combineReducers',
146 'redux.store',
147 'client.before',
148 'client.after',
149 'client.onRouterUpdate',
150 'client.onHistoryUpdate',
151 'server.reducers',
152 'server.inject',
153 'server.before',
154 'server.after',
155 'server.onRender',
156 'server.onRender.beforeDataToStore',
157 'server.onRender.afterDataToStore',
158 'inject',
159 ])
160
161 // console.log(projectConfig)
162 // 生成项目配置文件内容
163 let tempPortion = []
164 const propertiesPortion = [
165 'redux',
166 'server'
167 ]
168 const tempFull = propertiesToExtract.map(([key]) => {
169 let result = ''
170 if (key === 'server') {
171 if (isSPA) return ''
172 result = `export const ${key} = __SERVER__ ? ${JSON.stringify(projectConfig[key])} : {};`
173 } else {
174 result = `export const ${key} = ${JSON.stringify(projectConfig[key])};`
175 }
176 if (propertiesPortion.includes(key)) {
177 tempPortion.push(result)
178 }
179 return result
180 })
181 .join('\n')
182 .replace(/"require\((.+?)\).default"/g, `require($1).default`)
183
184 tempPortion = tempPortion.join('\n').replace(/"require\((.+?)\).default"/g, `require($1).default`)
185
186 // console.log(tempFull)
187
188 // 写入项目配置文件 (临时)
189 const pathFull = path.resolve(dirConfigTemp, filenameProjectConfigTempFull.replace(/\*/g, Date.now()))
190 process.env.KOOT_PROJECT_CONFIG_FULL_PATHNAME = pathFull
191 await fs.writeFile(pathFull, tempFull, 'utf-8')
192
193 const pathPortion = path.resolve(dirConfigTemp, filenameProjectConfigTempPortion.replace(/\*/g, Date.now()))
194 process.env.KOOT_PROJECT_CONFIG_PORTION_PATHNAME = pathPortion
195 await fs.writeFile(pathPortion, tempPortion, 'utf-8')
196
197 return {
198 ...validateBuildConfig(buildConfig),
199 [keyFileProjectConfigTempFull]: pathFull,
200 [keyFileProjectConfigTempPortion]: pathPortion
201 }
202
203 } else {
204 // 非完整配置情况,为兼容旧版本 (< 0.6) 而设
205 const buildConfig = await readBuildConfigFile()
206 return validateBuildConfig(buildConfig)
207 }
208}
209
210// 调整构建配置对象
211const validateBuildConfig = (config = {}) => {
212
213 // 改变配置项: dest -> dist
214 if (typeof config.dest !== 'undefined') {
215 config.dist = config.dest
216 delete config.dest
217 }
218 if (typeof config.dist !== 'undefined') {
219 validateConfigDist(config.dist)
220 }
221
222 // 改变配置项: webpack.config -> config
223 if (typeof config.webpack === 'object') {
224 /**
225 * 将配置中的 webpack 对象内的内容应用到配置对象顶层
226 * @param {String} nameInObject
227 * @param {String} nameAfter
228 */
229 const applyWebpackConfig = (nameInObject, nameAfter) => {
230 if (typeof config.webpack[nameInObject] !== 'undefined') {
231 config[nameAfter] = config.webpack[nameInObject]
232 delete config.webpack[nameInObject]
233 }
234 }
235 applyWebpackConfig('config', 'config')
236 applyWebpackConfig('beforeBuild', 'beforeBuild')
237 applyWebpackConfig('afterBuild', 'afterBuild')
238 applyWebpackConfig('defines', 'defines')
239 applyWebpackConfig('dll', 'webpackDll')
240 applyWebpackConfig('hmr', 'webpackHmr')
241 applyWebpackConfig('compilerHook', 'webpackCompilerHook')
242 Object.keys(config.webpack).forEach(key => {
243 applyWebpackConfig(key, 'webpack' + key.substr(0, 1).toUpperCase() + key.substr(1))
244 })
245 delete config.webpack
246 }
247
248 return config
249}