UNPKG

16.3 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3'use strict'
4const fs = require('fs')
5const path = require('path')
6// 文件变动检测
7const chokidar = require('chokidar')
8
9const Script = require('./lib/script')
10// 日志输出
11const { getLogger } = require('log4js')
12const logger = getLogger()
13
14// js预处理
15const postcss = require('postcss')
16const precss = require('precss')
17// css压缩
18const cssnano = require('cssnano')
19const autoprefixer = require('autoprefixer')
20
21const bodyHandle = require('./lib/page')
22const Cut = require('./lib/cut')
23// 删除目录所有内容
24const DELDIR = require('./lib/delDir')
25
26// Web 框架
27const express = require('express')
28const app = express()
29// 使express处理ws请求
30const wsServe = require('express-ws')(app)
31
32
33// 打包的版本号
34let version = ''
35
36// 配置日志输出等级
37// logger.level = 'debug'
38logger.level = 'info'
39
40// 命令行运行目录
41const runPath = process.cwd()
42let startTime = null
43
44// 当前打包的模板
45let htmlTemple = ''
46
47// 判断运行目录下是否包含配置文件
48if (!fs.readFileSync(path.join(runPath, 'ozzx.js'))) {
49 logger.error('ozzx.js file does not exist!')
50 close()
51}
52
53// 读取配置文件
54const config = eval(fs.readFileSync(path.join(runPath, 'ozzx.js'), 'utf8'))
55
56// 输出目录
57const outPutPath = path.join(runPath, config.outFolder)
58const corePath = path.join(__dirname, 'core')
59
60// 读取指定目录文件
61function loadFile(path) {
62 if (fs.existsSync(path)) {
63 return fs.readFileSync(path, 'utf8')
64 } else {
65 logger.error(`file does not exist: ${path}`)
66 return ''
67 }
68}
69
70// 处理style
71function handleStyle(dom, changePath) {
72 let styleData = ''
73 // 版本号后缀
74 const versionString = config.outPut.outFileAddVersion ? `.${version}` : ''
75 let outPutCss = dom.style
76 // 读取出全局样式
77 if (config.outPut.globalStyle) {
78 const mainStylePath = path.join(runPath, config.outPut.globalStyle)
79 if (fs.existsSync(mainStylePath)) {
80 const mainStyle = fs.readFileSync(path.join(runPath, config.outPut.globalStyle), 'utf8') + '\r\n'
81 // 混合css
82 outPutCss = mainStyle + outPutCss
83 } else {
84 logger.error(`globalStyle file not find!`)
85 }
86 }
87
88 // --------------------------------- 动画效果 ---------------------------------------------
89 // 判断是自动判断使用的动画效果还是用户指定
90 if (config.outPut.choiceAnimation) {
91 logger.debug('用户设置加载全部动画效果!')
92 // 加载全部特效
93 const animationFilePath = path.join(corePath, 'animation', `animations.css`)
94 outPutCss += loadFile(animationFilePath)
95 } else {
96 const useAnimationList = config.outPut.useAnimationList || dom.useAnimationList
97 useAnimationList.forEach(animationName => {
98 const animationFilePath = path.join(corePath, 'animation', `${animationName}.css`)
99 outPutCss += loadFile(animationFilePath)
100 })
101 }
102
103 // ----------------------------------------------- 使用postcss处理 -----------------------------------------------
104 // 自动加浏览器前缀
105 // console.log(autoprefixer.process)
106 let plugList = [precss, autoprefixer]
107 // 判断是否压缩优化css
108 if (config.minifyCss) {
109 plugList.push(cssnano)
110 }
111 postcss(plugList).process(outPutCss, { from: undefined, cascade: true }).then( (result) => {
112 const styleDir = path.join(outPutPath, 'css')
113 result.warnings().forEach((warn) => {
114 console.warn(warn.toString());
115 })
116 // console.log('css处理完毕!')
117 dom.style = result.css
118 // ----------------------------------------------- 输出css -----------------------------------------------
119 if (!changePath) {
120 DELDIR(styleDir)
121 logger.debug(`delete css dir success!`)
122 // 重新创建目录
123 fs.mkdirSync(styleDir)
124 }
125
126 styleData += `<link rel="stylesheet" href="./css/main${versionString}.css">`
127
128 fs.writeFileSync(path.join(outPutPath, 'css', `main${versionString}.css`), dom.style)
129
130
131 let completeNum = 0
132
133 // 如果没有额外的css直接输出
134 if (!config.styleList || config.styleList.length === 0) {
135 htmlTemple = htmlTemple.replace(`<!-- css-output -->`, styleData)
136 outPutHtml()
137 return
138 }
139 for (let ind = 0; ind < config.styleList.length; ind++) {
140 const element = config.styleList[ind]
141 // 判断是设置了路径
142 if (!element.src) {
143 console.error('style path unset!', element)
144 continue
145 }
146 // -------------sdsd---------------------------------------------------------
147 // 如果是网络地址那么不需要进行处理
148 if (element.src.startsWith('http')) {
149 styleData += `\r\n <link rel="stylesheet" href="${element.src}">`
150 if (++completeNum >= config.styleList.length) {
151 htmlTemple = htmlTemple.replace(`<!-- css-output -->`, styleData)
152 outPutHtml()
153 }
154
155 continue
156 } else {
157 styleData += `\r\n <link rel="stylesheet" href="./css/${element.name}.css">`
158 }
159 // 输出路径
160 const outPutFile = path.join(outPutPath, 'css', `${element.name}.css`)
161 if (changePath === undefined || changePath === path.join(runPath, element.src)) {
162 moveFile(path.join(runPath, element.src), outPutFile)
163 }
164 if (++completeNum >= config.styleList.length) {
165 htmlTemple = htmlTemple.replace(`<!-- css-output -->`, styleData)
166 outPutHtml()
167 }
168 }
169 })
170}
171
172// 处理heard
173function handleHrard(headList) {
174 // 取出所有Heard标识
175 let heardData = '<!-- 页面的元信息 -->'
176 headList.forEach(element => {
177 let heard = `\r\n <meta`
178 for (const key in element) {
179 const value = element[key]
180 heard += ` ${key}="${value}"`
181 }
182 heard += `/>`
183 heardData += `${heard}`
184 })
185 htmlTemple = htmlTemple.replace(`<!-- *head* -->`, heardData)
186 outPutHtml()
187}
188
189// 复制文件到指定路径
190function moveFile (fromPath, toPath) {
191 fs.readFile(fromPath, (err, fileData) => {
192 if (err) throw err
193 fs.writeFile(toPath, fileData, () => {
194 logger.info(`copy file: ${toPath}`)
195 })
196 })
197}
198
199// 处理script
200function handleScript (dom, changePath) {
201 // 版本号后缀
202 const versionString = config.outPut.outFileAddVersion ? `.${version}` : ''
203 // 根据不同情况使用不同的core
204 // 读取出核心代码
205 let coreScript = loadFile(path.join(corePath, 'main.js'))
206 if (config.pageList.length === 1) {
207 // 单页面
208 coreScript += loadFile(path.join(corePath, 'SinglePage.js'))
209 } else {
210 // 多页面
211 logger.info('multi page!')
212 coreScript += loadFile(path.join(corePath, 'MultiPage.js'))
213 }
214 // 页面切换特效
215 // 判断是否存在页面切换特效
216 const useAnimationList = config.outPut.useAnimationList || dom.useAnimationList
217 if (useAnimationList.length > 0 || config.outPut.choiceAnimation) {
218 logger.info('animation!')
219 coreScript += loadFile(path.join(corePath, 'animation.js'))
220 }
221 // 整合页面代码
222 coreScript += dom.script
223 // 处理使用到的方法
224 let toolList = Cut.stringArray(coreScript, 'ozzx.tool.', '(')
225 // 数组去重
226 toolList = new Set(toolList)
227 toolList.forEach(element => {
228 // console.log(element)
229 coreScript += loadFile(path.join(corePath, 'tool', `${element}.js`))
230 })
231
232 // 使用bable处理代码
233 dom.script = Script(coreScript, config.outPut.minifyJs).code
234
235 // ----------------------------------------------- 输出js -----------------------------------------------
236 const scriptDir = path.join(outPutPath, 'js')
237 let scriptData = '<!-- 页面脚本 -->'
238 if (!changePath) {
239 // 删除目录
240 DELDIR(scriptDir)
241 logger.debug(`delete script dir success!`)
242 // 重新创建目录
243 fs.mkdirSync(scriptDir)
244 }
245 // 写出主要硬盘文件
246 fs.writeFileSync(path.join(outPutPath, 'js' , `main${versionString}.js`), dom.script)
247
248 // 判断是否需要加入自动刷新代码
249 if (config.autoReload) {
250 if (!changePath) {
251 moveFile(path.join(corePath, 'debug', 'autoReload.js'), path.join(outPutPath, 'js', `autoReload.js`))
252 }
253 scriptData += '\r\n <script src="./js/autoReload.js" type="text/javascript"></script>'
254 }
255
256 // 处理引用的script
257 if (config.scriptList && config.scriptList.length > 0) {
258 // 遍历引用列表
259 let completeNum = 0
260 for (let ind = 0; ind < config.scriptList.length; ind++) {
261 const element = config.scriptList[ind]
262 // console.log(element)
263
264 // 判断是设置了路径
265 if (!element.src) {
266 console.error('script path unset!', element)
267 continue
268 }
269 // 如果是网络地址那么不需要进行处理
270 if (element.src.startsWith('http')) {
271 scriptData += `\r\n <script src="${element.src}" type="text/javascript" ${element.defer ? 'defer="defer"' : ''}></script>`
272 // 判断是否为最后项,如果为最后一项则输出script
273 if (++completeNum >= config.scriptList.length) {
274 // 如果有全局js则加入全局js
275 if (config.outPut.globalScript) {
276 const globalScriptData = fs.readFileSync(config.outPut.globalScript)
277 if (globalScriptData) {
278 logger.info(`add global script: ${config.outPut.globalScript}`)
279 scriptData += '\r\n<script>' + globalScriptData + '\r\n</script>'
280 } else {
281 logger.error('global script is set but file not found!')
282 }
283 }
284 scriptData += `\r\n <script src="./js/main${versionString}.js" type="text/javascript"></script>`
285 htmlTemple = htmlTemple.replace(`<!-- script-output -->`, scriptData)
286 outPutHtml()
287 }
288 continue
289 } else {
290 scriptData += `\r\n <script src="./js/${element.name}.js" type="text/javascript" ${element.defer ? 'defer="defer"' : ''}></script>`
291 }
292 // 输出路径
293 const outPutFile = path.join(outPutPath, 'js', `${element.name}.js`)
294 // 判断是否用babel处理
295 if (element.babel) {
296 if (changePath === undefined || changePath === path.join(runPath, element.src)) {
297 fs.readFile(path.join(runPath, element.src), (err, fileData) => {
298 if (err) throw err
299 fs.writeFile(outPutFile, Script(fileData, config.outPut.minifyJs).code, () => {
300 logger.info(`bable and out put file: ${outPutFile}`)
301 // 判断是否为最后项,如果为最后一项则输出script
302 if (++completeNum >= config.scriptList.length) {
303 // 如果有全局js则加入全局js
304 if (config.outPut.globalScript) {
305
306 const globalScriptData = fs.readFileSync(config.outPut.globalScript)
307 if (globalScriptData) {
308 logger.info(`add global script: ${config.outPut.globalScript}`)
309 scriptData += '\r\n <script>\r\n' + globalScriptData + '\r\n </script>'
310 } else {
311 logger.error('global script is set but file not found!')
312 }
313 }
314 scriptData += `\r\n <script src="./js/main${versionString}.js" type="text/javascript"></script>`
315 htmlTemple = htmlTemple.replace(`<!-- script-output -->`, scriptData)
316 outPutHtml()
317 }
318 })
319 })
320 } else {
321 if (++completeNum >= config.scriptList.length) {
322 // 如果有全局js则加入全局js
323 if (config.outPut.globalScript) {
324
325 const globalScriptData = fs.readFileSync(config.outPut.globalScript)
326 if (globalScriptData) {
327 logger.info(`add global script: ${config.outPut.globalScript}`)
328 scriptData += '\r\n <script>\r\n' + globalScriptData + '\r\n </script>'
329 } else {
330 logger.error('global script is set but file not found!')
331 }
332 }
333 scriptData += `\r\n <script src="./js/main${versionString}.js" type="text/javascript"></script>`
334 htmlTemple = htmlTemple.replace(`<!-- script-output -->`, scriptData)
335 outPutHtml()
336 }
337 }
338 } else {
339 // 如果不使用babel处理则进行复制文件
340 if (changePath === undefined || changePath === path.join(runPath, element.src)) {
341 moveFile(path.join(runPath, element.src), outPutFile)
342 }
343 if (++completeNum >= config.scriptList.length) {
344 // 如果有全局js则加入全局js
345 if (config.outPut.globalScript) {
346
347 const globalScriptData = fs.readFileSync(config.outPut.globalScript)
348 if (globalScriptData) {
349 logger.info(`add global script: ${config.outPut.globalScript}`)
350 scriptData += '\r\n <script>\r\n' + globalScriptData + '\r\n </script>'
351 } else {
352 logger.error('global script is set but file not found!')
353 }
354 }
355 scriptData += `\r\n <script src="./js/main${versionString}.js" type="text/javascript"></script>`
356 htmlTemple = htmlTemple.replace(`<!-- script-output -->`, scriptData)
357 outPutHtml()
358 }
359 }
360 }
361 } else {
362 // 如果没有引用script,则直接输出html
363 htmlTemple = htmlTemple.replace(`<!-- script-output -->`, scriptData)
364 outPutHtml()
365 }
366}
367
368
369function outPutHtml () {
370 // logger.info(htmlTemple)
371 if (!htmlTemple.includes('output')) {
372 fs.writeFileSync(path.join(outPutPath, 'index.html'), htmlTemple)
373 logger.info(`Package success! use time ${new Date().getTime() - startTime}`)
374
375 if (config.autoReload) {
376 // 广播发送重新打包消息
377 wsServe.getWss().clients.forEach(client => client.send('reload'))
378 }
379 }
380}
381// 执行默认打包任务
382function pack (changePath) {
383 // 生成版本号
384 if (!changePath) version = Math.random().toString(36).substr(2)
385
386 // 判断输出目录是否存在,如果不存在则创建目录
387 if (!fs.existsSync(outPutPath)) {
388 fs.mkdirSync(outPutPath)
389 }
390 // 记录开始打包时间
391 startTime = new Date().getTime()
392
393 // 读取入口模板文件(一次性读取到内存中)
394 htmlTemple = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8')
395 // 处理title
396 htmlTemple = htmlTemple.replace('{{title}}', config.title || 'ozzx')
397 handleHrard(config.headList)
398 const dom = bodyHandle(htmlTemple, config)
399 htmlTemple = dom.html
400 // 处理style
401 handleStyle(dom, changePath)
402 // 处理script
403 handleScript(dom, changePath)
404}
405
406// 开始打包
407pack()
408
409// 判断是否开启文件变动自动重新打包
410if (config.watcher.enable) {
411 let watcherFolder = config.watcher.folder
412 if (!watcherFolder) {
413 watcherFolder = './src'
414 logger.error('watcher is enable, but watcher.folder not set! use default value: "./src"')
415 } else {
416 watcherFolder = path.join(runPath, watcherFolder)
417 logger.info(`watcher folder: ${watcherFolder}`)
418 }
419 // 文件变动检测
420 const watcher = chokidar.watch(watcherFolder, {
421 // 忽略目录
422 ignored: config.watcher.ignored ? config.watcher.ignored : config.outFolder + '/*',
423 persistent: true,
424 usePolling: true,
425 // 检测深度
426 depth: config.watcher.depth
427 })
428
429 watcher.on('change', changePath => {
430 logger.info(`file change: ${changePath}`)
431 // 重新打包
432 pack(changePath)
433 })
434}
435
436// 判断是否启用静态文件服务
437if (config.server) {
438 app.use(express.static(path.join(runPath, config.outFolder)))
439}
440
441
442// 处理websocket消息
443if (config.autoReload) {
444 app.ws('/', function(ws, req) {
445 ws.on('message', function(msg) {
446 console.log(ws);
447 })
448 })
449}
450
451
452if (config.server || config.autoReload) {
453 const port = config.serverPort || 8000
454 app.listen(port)
455 logger.info(`server is running at 127.0.0.1:${port}`)
456}
457