gmi-web
Version:
Auto Build Website Program
619 lines (589 loc) • 24.5 kB
JavaScript
/**
* Description: Serve a project
* Author: Liam
* CreatedAt: 2017-1216-1216
* UpdateBy:
* UpdateAt:
*/
var webpack = require('webpack'),
// webpackConfig = require('../util/webpack.config.js'),
GMILOG = require ('../util/gmi-log'),
chokidar = require ('chokidar')
// async = require ('async')
// Generate webpack config object
var CommonsChunkPlugin = require ("webpack/lib/optimize/CommonsChunkPlugin"),
path = require ('path'),
fs = require ('fs'),
fsExtra = require ('fs-extra'),
uglifyJsPlugin = webpack.optimize.UglifyJsPlugin,
srcDir = path.resolve (process.cwd (), 'src'),
distDir = path.resolve (process.cwd (), 'dist')
// Render Html
var glob = require ('glob'),
minify = require ('html-minifier').minify
// Generate sass file
var sass = require ('node-sass')
if (fs.existsSync (path.resolve (process.cwd (), 'projectConfig.js'))) {
var projectConfig = require (path.resolve (process.cwd (), 'projectConfig.js'))
}
// Express
var express = require ('express')
var app = express ()
var opener = require ('opener')
// RunServer Object
RunServer = module.exports = {}
RunServer.run = function () {
RunServer.readyUp ()
RunServer.startServe ()
}
RunServer.readyUp = function () {
RunServer.webpackTool ()
RunServer.renderHtml ()
RunServer.renderSass ()
RunServer.copyAssets ()
// RunServer.copyJsLib ()
RunServer.copyLib ()
RunServer.watch ()
// RunServer.copyScssLib ()
}
RunServer.startServe = function () {
app.use (projectConfig.root, express.static (distDir))
// app.get(projectConfig.root, function (req, res) {
// res.send('Hello World')
// res.redirect ('/')
// })
if (projectConfig) {
app.listen (projectConfig.port)
var browserObject = opener ("http://localhost:" + projectConfig.port + projectConfig.root)
}
// console.log (browserObject)
}
RunServer.localhost = function (port) {
app.use ('/', express.static (path.resolve (process.cwd (), '')))
app.listen (port)
var browserTemp = opener ("http://localhost:" + port)
}
RunServer.renderSass = function () {
setTimeout ( () => {
sass.render ({
file: 'src/scss/pages/main.scss',
includePaths: ['src/scss/pages/'],
outFile: 'dist/css',
sourceMap: true,
outputStyle: 'compressed',
}, (error, result) => {
if (error) {
GMILOG.E_LOG (error)
} else {
fsExtra.outputFile ('dist/css/style.min.css', result.css, err => {
if (err) console.log (err)
})
}
})
}, 500)
}
RunServer.renderHtml = function () {
// console.log ("run render : " + srcDir + '/**/*.html')
glob ('src/pages/**/*.html', {}, (err, files) => {
// console.log (files)
if (err) {
console.log (err)
}
if (files.length < 1) {
console.log ("no files")
// return
}
files.forEach (path => {
fs.readFile (path, 'utf8', (err, content) => {
if (err) console.log (err)
renderRecursive (path, content)
})
})
})
}
RunServer.copyAssets = function () {
fsExtra.copy ('src/assets/', 'dist/assets/', (err) => {
if (err) console.log (err)
console.log ("Assets" + " copied!")
})
}
RunServer.copyJsLib = function () {
fsExtra.copy ('src/js/lib', 'dist/js/lib', (err) => {
if (err) console.log (err)
console.log ("js lib" + " copied!")
})
}
RunServer.copyLib = function () {
fsExtra.copy ('src/lib', 'dist/lib', (err) => {
if (err) console.log (err)
console.log ("lib" + " copied!")
})
}
RunServer.copyScssLib = function () {
fsExtra.copy ('src/scss/lib', 'dist/css/lib', (err) => {
if (err) console.log (err)
console.log ("scss lib" + " copied!")
})
}
/**
* add
* unlink
* unlinkDir
* change
*
*/
RunServer.watch = function () {
chokidar.watch ('src', {
// ignored: /^src(\\|\/)js(\\|\/)pages.*\.js$/,
ignoreInitial: true
}).on ('all', (event, path) => {
console.log (`event : ${event} ; path : ${path}`)
if (/^src(\\|\/)js(\\|\/)pages.*\.js$/.test (path)) {
console.log (path)
if (event == "add" || event == "unlink") {
RunServer.webpackTool (event)
}
} else if (/^src(\\|\/)pages.*\.html$/.test (path)) {
console.log(path)
RunServer.renderHtml ()
} else if (/^src(\\|\/)scss(\\|\/)pages.*\.scss$/.test (path)) {
RunServer.renderSass ()
} else if (/^src(\\|\/)assets*/.test (path)) {
console.log (path)
if (event == "add") {
fsExtra.copy (path, path.replace ('src', 'dist'), (err) => {
if (err) console.log (err)
console.log (path + " copied!")
})
}
} else if (/^src(\\|\/)lib.*\.js$/.test (path)) {
if (event != "unlink" && event != "unlinkDir") {
fsExtra.copy (path, path.replace ('src', 'dist'), (err) => {
if (err) console.log (err)
console.log (path + " copied!")
})
}
}
})
}
RunServer.webpackTool = function (event) {
if (event == "add" || event == "unlink") {
if (RunServer.watching) {
RunServer.watching.close (() => {
console.log ("restart watch")
})
}
if (RunServer.myDevConfig != null) {
RunServer.myDevConfig = null
}
if (RunServer.devCompiler != null) {
RunServer.devCompiler = null
}
var JsFileName = getJsFile (),
EntryFileName = getEntryName ()
//创建一个webpack配置对象
RunServer.myDevConfig = Object.create ({
//允许缓存提高性能
cache: true,
//生成sourcemap文件
devtool: projectConfig.sourceMap || "cheap-module-eval-source-map",
//多文件入口
entry: JsFileName,
output: {
//__diename返回当前文件所在的路劲
path: path.resolve (distDir, 'js/'),
//The publicPath specifies the public URL address of the output files when referenced in a browser.
// the url to the output directory resolved relative to the HTML page,网站运行时访问的路径
publicPath: path.resolve (distDir, 'js/'),
//生成文件的命名
filename: "[name].js",
//是未被列在entry中,却又需要被打包出来的文件命名配置,在按需加载(异步)模块的时候,这样的文件是没有被列在entry中的,如使用CommonJS的方式异步加载模块
/*
require.ensure(["modules/tips.jsx"], function(require) {
var a = require("modules/tips.jsx");
// ...
}, 'tips');
异步加载的模块是要以文件形式加载哦,所以这时生成的文件名是以chunkname配置的,生成出的文件名就是tips.min.js。
*/
chunkFilename: "[chunkhash].js"
},
resolve: {
//查找module的话从这里开始查找
//root: '/pomy/github/flux-example/src', //绝对路径
//自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
//extensions: ['', '.js', '.json', '.scss'],
//模块别名定义,方便后续直接引用别名,无须多写长长的地址
alias: {
// zepto: srcDir + "/js/lib/zepto.min.js",
core: srcDir + "/js/core",
util: srcDir + "/js/util",
tool: srcDir + "/js/tool",
}
},
module: {
//加载器配置
//解析出zepto的路径,然后使用加载器进行zepto的改变
rules: [{
test: /\.js$/,
exclude: [/node_modules/],
use: [{
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}],
},
// Loaders for other file types can go here
],
},
plugins: [
new CommonsChunkPlugin ({name: 'common', chunks: EntryFileName}),
new uglifyJsPlugin ({
// 最紧凑的输出
beautify: projectConfig.jsBeautify || true,
// 删除所有的注释
comments: projectConfig.jsComments || false,
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: projectConfig.jsWarnings || false,
// 删除所有的 `console` 语句
// 还可以兼容ie浏览器
drop_console: projectConfig.jsDrop_console || false,
// 内嵌定义了但是只用到一次的变量
collapse_vars: projectConfig.jsCollapse_vars || false,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: projectConfig.jsReduce_vars || false,
}
})
]
//当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。此时我们就可以通过配置externals参数来解决这个问题
// externals: {
// "jquery": "jQuery"
// }
})
RunServer.devCompiler = webpack (RunServer.myDevConfig)
RunServer.watching = RunServer.devCompiler.watch ({ // watch options:
aggregateTimeout: 300, // wait so long for more changes
poll: false // use polling instead of native watchers
// pass a number to set the polling interval
}, function (err, stats) {
if (err) GMILOG.E_LOG (err)
// GMILOG.N_LOG ("-")
if (!RunServer.statsHash) {
RunServer.statsHash = stats.hash
GMILOG.N_LOG (stats.toString ({
profile: true, colors: true
}))
} else {
if (RunServer.statsHash != stats.hash) {
GMILOG.N_LOG (stats.toString ({
profile: true, colors: true
}))
}
}
})
} else {
var JsFileName = getJsFile (),
EntryFileName = getEntryName ()
//创建一个webpack配置对象
RunServer.myDevConfig = Object.create ({
//允许缓存提高性能
cache: true,
//生成sourcemap文件
devtool: projectConfig.sourceMap || "cheap-module-eval-source-map",
//多文件入口
entry: JsFileName,
output: {
//__diename返回当前文件所在的路劲
path: path.resolve (distDir, 'js/'),
//The publicPath specifies the public URL address of the output files when referenced in a browser.
// the url to the output directory resolved relative to the HTML page,网站运行时访问的路径
publicPath: path.resolve (distDir, 'js/'),
//生成文件的命名
filename: "[name].js",
//是未被列在entry中,却又需要被打包出来的文件命名配置,在按需加载(异步)模块的时候,这样的文件是没有被列在entry中的,如使用CommonJS的方式异步加载模块
/*
require.ensure(["modules/tips.jsx"], function(require) {
var a = require("modules/tips.jsx");
// ...
}, 'tips');
异步加载的模块是要以文件形式加载哦,所以这时生成的文件名是以chunkname配置的,生成出的文件名就是tips.min.js。
*/
chunkFilename: "[chunkhash].js"
},
resolve: {
//查找module的话从这里开始查找
//root: '/pomy/github/flux-example/src', //绝对路径
//自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
//extensions: ['', '.js', '.json', '.scss'],
//模块别名定义,方便后续直接引用别名,无须多写长长的地址
alias: {
// zepto: srcDir + "/js/lib/zepto.min.js",
core: srcDir + "/js/core",
util: srcDir + "/js/util",
tool: srcDir + "/js/tool"
}
},
module: {
//加载器配置
//解析出zepto的路径,然后使用加载器进行zepto的改变
rules: [{
test: /\.js$/,
exclude: [/node_modules/],
use: [{
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}],
},
// Loaders for other file types can go here
],
},
plugins: [
new CommonsChunkPlugin ({name: 'common', chunks: EntryFileName}),
new uglifyJsPlugin ({
// 最紧凑的输出
beautify: projectConfig.jsBeautify || true,
// 删除所有的注释
comments: projectConfig.jsComments || false,
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: projectConfig.jsWarnings || false,
// 删除所有的 `console` 语句
// 还可以兼容ie浏览器
drop_console: projectConfig.jsDrop_console || false,
// 内嵌定义了但是只用到一次的变量
collapse_vars: projectConfig.jsCollapse_vars || false,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: projectConfig.jsReduce_vars || false,
}
})
]
//当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。此时我们就可以通过配置externals参数来解决这个问题
// externals: {
// "jquery": "jQuery"
// }
})
RunServer.devCompiler = webpack (RunServer.myDevConfig)
RunServer.watching = RunServer.devCompiler.watch ({ // watch options:
aggregateTimeout: 300, // wait so long for more changes
poll: false // use polling instead of native watchers
// pass a number to set the polling interval
}, function (err, stats) {
if (err) GMILOG.E_LOG (err)
// GMILOG.N_LOG ("-")
if (!RunServer.statsHash) {
RunServer.statsHash = stats.hash
GMILOG.N_LOG (stats.toString ({
profile: true, colors: true
}))
} else {
if (RunServer.statsHash != stats.hash) {
GMILOG.N_LOG (stats.toString ({
profile: true, colors: true
}))
}
}
})
}
// RunServer.devCompiler.run ((err, stats) => {
// if (err) GMILOG.E_LOG (err)
// // GMILOG.N_LOG ("-")
// if (!RunServer.statsHash) {
// RunServer.statsHash = stats.hash
// GMILOG.N_LOG (stats.toString ({
// profile: true, colors: true
// }))
// } else {
// if (RunServer.statsHash != stats.hash) {
// GMILOG.N_LOG (stats.toString ({
// profile: true, colors: true
// }))
// }
// }
// })
}
function renderRecursive (path, content) {
// console.log (content + "\n-------" + path)
let regexOuter = /<\%\ \@\@include="(.*?)"\ \%>/
let regexInclude = /include="(.*?)"/
let regexInner = />(.*?)</
let rootReg = /\@PATH\@/
let versionReg = /\@VERSION\@/
// let COMMENT_PSEUDO_COMMENT_OR_LT_BANG = new RegExp(
// '<!--[\\s\\S]*?(?:-->)?'
// + '<!---+>?' // A comment with no body
// + '|<!(?![dD][oO][cC][tT][yY][pP][eE]|\\[CDATA\\[)[^>]*>?'
// + '|<[?][^>]*>?', // A pseudo-comment
// 'g');
let cleanComment = /\<\!\-\-(\ |)(.*?)\-\-\>/
while (cleanComment.test (content)) {
content = content.replace (cleanComment, '')
}
// while (COMMENT_PSEUDO_COMMENT_OR_LT_BANG.test (content)) {
// content = content.replace (COMMENT_PSEUDO_COMMENT_OR_LT_BANG, '')
// }
content = content.replace (regexOuter, match => {
let matchInclude = match
let contentUpdated = ''
// console.log (content)
// console.log (matchInclude)
match.replace (regexInclude, match => {
// console.log (match)
match.replace (/"(.*?)"/, matchIncludeFile => {
let includeFileName = matchIncludeFile.substring (1, matchIncludeFile.length - 1)
// console.log (path)
let pathRelative = path.split ('/').slice (0, -1).join ('/')
// console.log (pathRelative)
if (/^\//.test (includeFileName)) {
includeFileName = includeFileName.replace (/^\//, '')
pathRelative = "src/pages"
}
if (/^includes\//.test (includeFileName)) {
includeFileName = includeFileName.replace (/^includes\//, '')
pathRelative = "src/pages/includes"
}
while (true) {
if (/^\.\//.test (includeFileName)) {
includeFileName = includeFileName.replace (/^\.\//, '')
} else {
break
}
}
while (true) {
if (/^\.\.\//.test (includeFileName)) {
includeFileName = includeFileName.replace (/^\.\.\//, '')
pathRelative = pathRelative.split ('/').slice (0, -1).join ('/')
} else {
break
}
}
// console.log (includeFileName)
// includeFileName.replace (/^\.\//, currentPath => {
// console.log (currentPath)
// })
// console.log (includeFileName)
let includeFilePath = pathRelative + "/" + includeFileName
// contentUpdated = ' ' + matchInclude.replace (regexOuter, '').trim ()
contentUpdated = matchInclude.replace (regexOuter, match => {
let content = ''
// console.log (includeFilePath)
if (!fs.existsSync (includeFilePath)) {
GMILOG.E_LOG (includeFilePath + " File not found!")
} else {
content = fs.readFileSync (includeFilePath, 'utf8')
return '' + content + ''
}
})
})
})
return contentUpdated
})
// console.log (content + "\n-------" + path)
// console.log (/<\%\ \@\@include="(.*?)"\ \%>/.test (content))
if (/<\%\ \@\@include="(.*?)"\ \%>/.test (content)) {
renderRecursive (path, content)
} else {
let filename = path.replace (/^src/, 'dist').replace (/pages\//, '')
while (versionReg.test (content)) {
content = content.replace (versionReg, projectConfig.version)
}
while (rootReg.test (content)) {
content = content.replace (rootReg, projectConfig.root)
}
// console.log (filename)
fsExtra.outputFile (filename, minify (content, {collapseWhitespace: true, removeComments: true}), err => {
if (err) console.log (err)
})
}
}
//获取所有js\pages目录下的文件夹名称
function getDirName () {
var fileList = []
var jsPath = path.resolve (srcDir, 'js/pages')
// GMILOG.N_LOG (jsPath);
if (!fs.existsSync (jsPath)) {
GMILOG.E_LOG (jsPath + " is not exist!")
} else {
var dirs = fs.readdirSync (jsPath)
dirs.forEach (function (item) {
fileList.push (item)
})
}
// GMILOG.N_LOG (fileList);
return fileList
}
//遍历获取js文件的数组
function getJsFile () {
//获取js目录下所有文件夹名字数组
var fileList = getDirName ()
var totalFiles = {}
var matchs = [],
files = {}
if (fileList.length > 0) {
fileList.forEach (function (fileItem) {
if (fileItem.match (/(.+)\.js$/)) {
matchs = fileItem.match (/(.+)\.js$/)
if (matchs) {
files[matchs[1]] = path.resolve (srcDir, 'js/pages', fileItem)
}
} else {
var jsPath = path.resolve (srcDir, 'js/pages/' + fileItem)
// if (!fs.existsSync (jsPath)) {
// GMILOG.E_LOG (jsPath + " is not exist!")
// } else {
var dirs = fs.readdirSync (jsPath)
dirs.forEach (function (item) {
matchs = item.match (/(.+)\.js$/)
if (matchs) {
files[matchs[1]] = path.resolve (srcDir, 'js/pages/' + fileItem, item)
}
});
// }
}
})
}
return files
//synchronous read dir,return an array of filenames excluding '.' and '..'
// console.log("this is detected js: " + dirs);
//declare matchs array and file object will be used later
//use reg match any.js file
}
//遍历获取js文件的数组
function getEntryName () {
//获取js目录下所有文件夹名字数组
var fileList = getDirName ()
var totalFiles = {}
var entryName = []
var matchs = [],
files = {}
if (fileList.length > 0) {
fileList.forEach (function (fileItem) {
if (fileItem.match (/(.+)\.js$/)) {
matchs = fileItem.match (/(.+)\.js$/)
if (matchs) {
entryName.push (matchs[1]);
}
} else {
var jsPath = path.resolve (srcDir, 'js/pages/' + fileItem)
// if (!fs.existsSync (jsPath)) {
// GMILOG.E_LOG (jsPath + " is not exist!")
// } else {
var dirs = fs.readdirSync (jsPath)
dirs.forEach (function (item) {
matchs = item.match (/(.+)\.js$/)
if (matchs) {
entryName.push (matchs[1])
}
})
// }
}
})
}
return entryName;
//synchronous read dir,return an array of filenames excluding '.' and '..'
// console.log("this is detected js: " + dirs);
//declare matchs array and file object will be used later
//use reg match any.js file
}