UNPKG

7 kBJavaScriptView Raw
1'use strict'
2
3const fs = require('fs')
4const path = require('path')
5const glob = require('glob')
6const devIp = require('dev-ip')
7const portscanner = require('portscanner')
8const ExtractTextPlugin = require('extract-text-webpack-plugin')
9
10// 【注意】utils.js 为纯工具库,请不要依赖 config/index.js
11
12// From create-react-app
13// Make sure any symlinks in the project folder are resolved:
14// https://github.com/facebookincubator/create-react-app/issues/637
15const appDirectory = fs.realpathSync(process.cwd())
16
17function rootPath(relativePath) {
18 return path.resolve(appDirectory, relativePath)
19}
20
21/**
22 * 获取入口文件名列表
23 * @return {Array} 入口名数组
24 */
25function getPageList(entry) {
26 const entries = getEntries(`${process.cwd()}/${entry}`)
27 return Object.keys(entries)
28}
29
30/**
31 * 获取本机局域网 ip
32 * @return {String} ip
33 */
34function localIp() {
35 const ip = devIp()
36 // vpn 下 ip 为数组,第一个元素为本机局域网 ip
37 // 第二个元素为 vpn 远程局域网 ip
38 return ip ? (Array.isArray(ip) ? ip[0] : ip) : '0.0.0.0'
39}
40
41/**
42 * 获取空闲端口号,范围 [start, start + 20]
43 * @return {Number} 端口号
44 */
45async function getFreePort(defPort) {
46 const ceiling = Number(defPort + 20)
47
48 return portscanner.findAPortNotInUse(defPort, ceiling, localIp())
49}
50
51/**
52 * 获取指定路径下的入口文件
53 * @param {String} globPath 通配符路径
54 * @param {String} preDep 前置模块
55 * @return {Object} 入口名:路径 键值对
56 * {
57 * pageA: ['a.js'],
58 * pageB: ['b.js']
59 * }
60 */
61function getEntries(globPath, preDep = []) {
62 const files = glob.sync(rootPath(globPath))
63 const getPageName = filepath => {
64 const dirname = path.dirname(path.relative('src/view/', filepath))
65 // 兼容组件,src/index.js
66 return dirname === '..' ? 'index' : dirname
67 }
68
69 // glob 按照字母顺序取 .js 与 .ts 文件
70 // 通过 reverse 强制使 js 文件在 ts 之后,达到覆盖目的
71 // 保证 index.js 优先原则
72 return files.reverse().reduce((entries, filepath) => {
73 const name = getPageName(filepath)
74 // preDep 支持数组或字符串。所以这里使用 concat 方法
75 entries[name] = [].concat(preDep, filepath)
76
77 return entries
78 }, {})
79}
80
81function getChunks(globPath, preDep = []) {
82 const files = glob.sync(rootPath(globPath))
83 const getTrunkName = filepath => {
84 const basename = path.posix.basename(filepath, '.js')
85 return basename.replace(/^index\./, '') + '.servant'
86 }
87
88 return files.reduce((trunks, filepath) => {
89 const name = getTrunkName(filepath)
90 // preDep 支持数组或字符串。所以这里使用 concat 方法
91 trunks[name] = [].concat(preDep, filepath)
92
93 return trunks
94 }, {})
95}
96
97/**
98 * 解析日期
99 * @param {Date | Number} target 日期对象或时间戳
100 * @return {Object} 结果对象
101 */
102function parseDate(target) {
103 const f = n => (n > 9 ? n : '0' + n)
104 const date = target instanceof Date ? target : new Date(target)
105 return {
106 y: date.getFullYear(),
107 M: f(date.getMonth() + 1),
108 d: f(date.getDate()),
109 h: f(date.getHours()),
110 m: f(date.getMinutes()),
111 s: f(date.getSeconds())
112 }
113}
114
115/**
116 * 格式化日期为 yyyy-MM-dd 格式
117 * @param {Date | Number} dt 日期对象或时间戳
118 * @return {String} 格式化结果
119 */
120function pubDate(dt) {
121 const date = parseDate(dt)
122 return `${date.y}-${date.M}-${date.d}`
123}
124
125/**
126 * 生成 banner
127 * @return {String} 包含项目版本号,构建日期
128 */
129function banner() {
130 return (
131 `@version ${process.env.npm_package_version}\n` +
132 `@date ${pubDate(new Date())}\n` +
133 // 对 bundle 文件添加 @generated 标识
134 // 在 code review 面板忽略相关 diff
135 `@generated`
136 )
137}
138
139function nodeModulesRegExp(modules = '') {
140 // path.sep 指定平台特定的分隔符
141 // Windows: \ POSIX: /
142 // 参考:http://nodejs.cn/api/path.html#path_path_sep
143 return []
144 .concat(modules)
145 .map(mod => new RegExp(`node_modules\\${path.sep}${mod}?`))
146}
147
148function isNotEmptyArray(target) {
149 return Array.isArray(target) && target.length
150}
151
152function isObject(obj) {
153 return Object.prototype.toString.call(obj) === '[object Object]'
154}
155
156function ensureSlash(path, needsSlash) {
157 const hasSlash = path.endsWith('/')
158 if (hasSlash && !needsSlash) {
159 return path.substr(path, path.length - 1)
160 } else if (!hasSlash && needsSlash) {
161 return `${path}/`
162 } else {
163 return path
164 }
165}
166
167function camelName(name) {
168 return name
169}
170
171// https://stackoverflow.com/questions/20270973/nodejs-spawn-stdout-string-format
172function buffer2String(data) {
173 return data.toString().replace(/[\n\r]/g, '')
174}
175
176function write(dest, code) {
177 return new Promise((resolve, reject) => {
178 fs.writeFile(dest, code, err => {
179 if (err) return reject(err)
180 resolve()
181 })
182 })
183}
184
185function assetsPath(_path) {
186 return path.posix.join('static', _path)
187}
188
189function cssLoaders(options) {
190 options = options || {}
191
192 var cssLoader = {
193 loader: 'css-loader',
194 options: {
195 minimize: process.env.NODE_ENV === 'production',
196 sourceMap: options.sourceMap
197 }
198 }
199
200 var postcssLoader = {
201 loader: 'postcss-loader',
202 options: {
203 sourceMap: true
204 }
205 }
206
207 var px2rpxLoader = {
208 loader: 'px2rpx-loader',
209 options: {
210 baseDpr: 1,
211 rpxUnit: 0.5
212 }
213 }
214
215 // generate loader string to be used with extract text plugin
216 function generateLoaders(loader, loaderOptions) {
217 var loaders = [cssLoader, postcssLoader, px2rpxLoader]
218 if (loader) {
219 loaders.push({
220 loader: loader + '-loader',
221 options: Object.assign({}, loaderOptions, {
222 sourceMap: options.sourceMap
223 })
224 })
225 }
226
227 // Extract CSS when that option is specified
228 // (which is the case during production build)
229 if (options.extract) {
230 return ExtractTextPlugin.extract({
231 use: loaders,
232 fallback: 'vue-style-loader'
233 })
234 } else {
235 return ['vue-style-loader'].concat(loaders)
236 }
237 }
238
239 // https://vue-loader.vuejs.org/en/configurations/extract-css.html
240 return {
241 css: generateLoaders(),
242 postcss: generateLoaders(),
243 less: generateLoaders('less'),
244 sass: generateLoaders('sass', { indentedSyntax: true }),
245 scss: generateLoaders('sass'),
246 stylus: generateLoaders('stylus'),
247 styl: generateLoaders('stylus')
248 }
249}
250
251// Generate loaders for standalone style files (outside of .vue)
252function styleLoaders(options) {
253 var output = []
254 var loaders = cssLoaders(options)
255 for (var extension in loaders) {
256 var loader = loaders[extension]
257 output.push({
258 test: new RegExp('\\.' + extension + '$'),
259 use: loader
260 })
261 }
262 return output
263}
264
265module.exports = {
266 styleLoaders,
267 cssLoaders,
268 assetsPath,
269 isObject,
270 write,
271 getPageList,
272 localIp,
273 getFreePort,
274 getEntries,
275 getChunks,
276 rootPath,
277 parseDate,
278 pubDate,
279 banner,
280 isNotEmptyArray,
281 nodeModulesRegExp,
282 ensureSlash,
283 camelName,
284 buffer2String
285}