1. 介绍 ucf-scripts是bip平台统一的前端工程脚手架。
2. 目录
目录文件说明
base.config.js 基础配置文件
start.js 本地起服务入口文件
build.js 打包服务入口文件
start.config.js 本地起服务配置文件
build.config.js 打包配置文件
utils.js 工具方法
3. Plugins插件 3.1 start.config.js 3.1.1. open-browser-plugin 项目启动后自动打开浏览器
3.1.2. html-webpack-plugin html-webpack-plugin 为应用程序生成一个 HTML 文件,并自动将生成的所有 bundle 注入到此文件中。
使用
说明
filename 生成的模版文件的名字
template 模版来源文件
inject 引入模板的注入位置,取值有true/false/body/head
true:默认值,script标签位于html文件的body底部
body:script标签位于html文件的body底部
head:script标签位于html文件的head中
false:不插入生成的js文件,这个几乎不会用到
chunks 引入的模块,这里指定的是entry中设置多个js时,在这里指定引入的js,如果不设置则默认全部引入
hash
true 打包生成的html文件引入的js/css带有hash值
static_url
困惑解析使用require.resolve函数来查询某个模块文件的带有完整绝对路径的文件名
require.resolve(url)
3.1.3. HotModuleReplacementPlugin 热更新模块
1 2 3 4 plugins: [ new webpack.HotModuleReplacementPlugin(), ...HtmlPlugin ]
3.1.4. DefinePlugin 允许在 编译时 将你代码中的变量替换为其他值或表达式
1 2 cfg.global_env && (config.plugins = config.plugins.concat(new webpack.DefinePlugin(cfg.global_env)));
3.1.5. @tinper/next-plugin 统一前端三方包接入Plugin
3.2. base.config.js 3.2.1. BannerPlugin webpack内置Plugin,在打包好的js文件最开始处添加版权声明。
1 2 3 4 5 plugins: [ new webpack.BannerPlugin({ banner : `File:[file] Date:${new Date ()} ` }) ]
原本的状态是css-in-js,使用该插件后可以将css单独打包成一个文件。
1 2 3 4 5 6 7 plugins: [ new MiniCssExtractPlugin({ filename : '[name].css' , chunkFilename : '[name].css' , ignoreOrder : true }), ]
3.2.3. ProgressPlugin webpack构建进度条。
1 new webpack.ProgressPlugin()
3.3 build.config.js 3.3.1. CleanWebpackPlugin 删除指定目录下的文件。在项目中主要用来在每一次编译前,先删除上一次编译完后的代码。
3.3.2. OptimizeCSSAssetsPlugin 压缩css代码
3.3.3. TerserWebpackPlugin
webpack5已内置该插件,但webpack v4并没有内置。
该插件使用 terser 来压缩 JavaScript。
3.3.4. html-webpack-plugin 3.3.5. TNSClientPlugin 4. Loader
loader的顺序是从下往上,从右往左
4.1. start.config.js 无 4.2. base.config.js 4.2.1. babel-loader babel-loader可以将ES6、ES7等一些浏览器不支持的高级语法编译成浏览器可以识别的ES5语法。
4.2.2. css-loader 对@import和url()进行处理,就像js解析import/require()一样。
4.2.3. postcss-loader 进一步处理css文件,比如添加浏览器前缀,压缩CSS等。
4.2.4. less-loader 将Less编译成css
4.2.5. style-loader 将css插入到dom中,但是我们一般会使用MiniCSSExtractPlugin将CSS打包成独立的css文件,并通过HtmlWebpackPlugin引入css文件,所以可以不用这个loader
4.2.6. url-loader 将文件作为 data URI 内联到 bundle 中
4.2.7. file-loader 将文件发送到输出目录
4.3 build.config.js 无 5. Middleware 5.1 start.config.js 5.1.1. http-proxy-middleware 代理中间件,用于转发请求,将客户端发送的请求数据转发到目标服务器,再将响应输送到客户端。
5.1.2. webpack-dev-middleware 生成一个与webpack的compiler绑定的中间件,在express启动的服务app中调用这个中间件。 作用
通过watch mode,监听资源的变更,自动打包
快速编译,走内存 !!!
返回中间件,支持express的use格式
5.1.3. webpack-hot-middleware 实现页面热更新,一般与webpack-dev-middleware配合使用
6. 分析 本地起服务走start.js, 编译走build.js。
6.1 base.config.js 1 2 3 4 5 6 7 const config = { output : {}, module : {}, resolve : {}, plugins : {} }
6.1.1. output 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const config = { output : { path : path.resolve('.' , dist_root, _context), filename : '[name].js' , chunkFilename : '[name].js' , publicPath : cfg.publicPath ? '/' + _context : undefined , environment : { arrowFunction : false , bigIntLiteral : false , const : false , destructuring : false , dynamicImport : false , forOf : false , module : false , } }, }
6.1.2. module
使用相对应的loader对js/less/css/图片/图标字体/svg等做处理编译
6.1.3. resolve 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const config = { resolve : { extensions : [ ".jsx" , ".js" , ".less" , ".css" , ".json" ], alias : { 'ucf-apps' : path.resolve('.' , 'ucf-apps/' ), 'ucf-common' : path.resolve('.' , 'ucf-common/src/' ), components : path.resolve('.' , 'ucf-common/src/components/' ), static : path.resolve('.' , 'ucf-common/src/static/' ), utils : path.resolve('.' , 'ucf-common/src/utils/' ) } }, }
6.1.4. plugins 1 2 3 4 5 6 7 8 9 10 11 12 13 const config = { plugins : [ new webpack.BannerPlugin({ banner : `File:[file] Date:${new Date ()} ` }), new MiniCssExtractPlugin({ filename : '[name].css' , chunkFilename : '[name].css' , ignoreOrder : true }), new webpack.ProgressPlugin() ] }
6.2 start.js
本地起服务的时候,调用server方法,并传入{port, ip}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const getPort = require ('get-port' );const ip = require ('ip' );() => { getPort({ port : commands.port || 3000 }).then(port => { server({ port, ip : ip.address() }); }); }
server方法定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 const webpackConfig = require ('./start.config' );const compiler = webpack(webpackConfig);server = opt => { const instance = devMiddleware(compiler, { publicPath : webpackConfig.output.publicPath, headers : { 'Access-Control-Allow-Origin' : '*' , 'Ucf-Server' : util.getPkg().version }, stats : { colors : true , hash : false , children : false , chunks : false } }); cfg.static && app.use((cfg.context == '' || cfg.context == undefined ) ? '' : `/${cfg.context} ` , express.static(path.resolve("." , cfg.static))); app.use(instance); app.use(hotMiddleware(compiler)); cfg.proxy && cfg.proxy.forEach(function (element ) { if (element.enable) { let proxyOpt = { target : element.url, logLevel : "debug" , changeOrigin : true , pathRewrite : Object .assign({}, element.pathRewrite), headers : (typeof element.headers !== 'undefined' ? element.headers : {}), onProxyRes : function (proxyRes ) { proxyRes.headers["Ucf-Proxy" ] = "success" ; } } app.use(element.router, createProxyMiddleware(element.opts || proxyOpt)); } }); app.listen(opt.port, host, () => { console .log(); console .log(chalk.green(`----------------------------------------------------` )); console .log(chalk.yellow(`\t 🚀 UCF Develop Server` )); console .log(chalk.green(`\t [Server Version]: 🏅 ${util.getPkg().version} ` )); console .log(chalk.green(`\t [Local] : 🏠 http://${browserHost} :${opt.port} ` )); console .log(chalk.green(`\t [Lan] : 📡 http://${opt.ip} :${opt.port} ` )); console .log(chalk.green(`----------------------------------------------------` )); console .log(); }); }
6.3 start.config.js
大致的配置,会与base.config.js合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const base = require ('./base.config' );const config = { devtool : 'source-map' , mode : 'development' , externals : cfg.externals, resolve : { alias : cfg.alias }, module : { rules : cfg.loader }, plugins : [ new webpack.HotModuleReplacementPlugin(), ...HtmlPlugin ] } config.entry = entries; cfg.global_env && (config.plugins = config.plugins.concat(new webpack.DefinePlugin(cfg.global_env))); cfg.devPlugins && (config.plugins = config.plugins.concat(cfg.devPlugins)); module .exports = merge(base, config);
HtmlPlugin的相关配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 const HtmlPlugin = [];if (commands.homepage) { HtmlPlugin.push(new OpenBrowserPlugin({ url : `http://${browserHost} :${commands.port || 3000 } /${commands.homepage || '' } ` })); } glob.sync('./ucf-common/src/portal/src/app.js' ).forEach(_path => { entries['index' ] = './ucf-common/src/portal/src/app.js' ; const htmlConf = { filename : `index.html` , template : './ucf-common/src/portal/src/index.html' , inject : 'body' , chunks : ['index' ], hash : true }; HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); }); bootFiles.forEach(_path => { let _context = "" ; if (!cfg.publicPath && cfg.context) { _context = `${cfg.context} /` ; } const module = _path.split(`./${scan_root} /` )[1 ].split(`/src/${bootName} ` )[0 ]; const chunk = `${_context} ${module } /index` ; const targetDir = _path.split(`/${bootName} ` )[0 ] const templateType = cfg.templateType ? cfg.templateType : 'html' const htmlConf = Object .assign({ filename : `${chunk} .html` , template : `${targetDir} /index.${templateType} ` , inject : 'body' , chunks : [chunk], hash : true , static_url : cfg.static_url ? cfg.static_url : '' },HtmlPluginConf); if (bootList && typeof bootList == 'boolean' ) { entries[chunk] = [_path, require .resolve('./webpack-hot-middleware/client' )]; HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); } else if (Array .isArray(bootList) && bootList.length > 0 ) { bootList.forEach(item => { _bootList.add(item); }); if (_bootList.has(module )) { entries[chunk] = [_path, require .resolve('./webpack-hot-middleware/client' )]; HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); } } }); if (tnsEnabled) { HtmlPlugin.push(new TNSClientPlugin({libraryDebug : true , ...tnsDetailCfg})); }
6.4 build.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const webpackConfig = require ('./build.config' );const compiler = webpack(webpackConfig);module .exports = { plugin : () => { build(); } } build = () => { console .log(); console .log(chalk.green(`--------------------------------------------` )); console .log(chalk.yellow(`\t 🚀 UCF Build Server` )); console .log(chalk.green(`\t [Build Version] : 🏅 ${util.getPkg().version} ` )); console .log(); console .log(chalk.green(`\t 💪 Good Luck Please Wait ☃️` )); console .log(chalk.green(`--------------------------------------------` )); console .log(); compiler.run((err, stats ) => { if (!err) { console .log('\n' + stats.toString({ hash : false , chunks : false , children : false , colors : true })); } else { console .log(chalk.red(err)); } }); }
6.5. build.config.js
大致的配置,会与base.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const config = { output, mode : 'production' , devtool : 'cheap-module-source-map' , externals : cfg.externals, resolve : { alias : cfg.alias }, module : { rules : cfg.loader }, optimization : { minimize : false }, plugins : [ new CleanWebpackPlugin(), new OptimizeCSSAssetsPlugin({ cssProcessorOptions : { safe : true , mergeLonghand : false , discardComments : { removeAll : true } }, canPrint : true }), new TerserPlugin({ parallel : true , }), ...HtmlPlugin ] } config.entry = entries; cfg.global_env && (config.plugins = config.plugins.concat(new webpack.DefinePlugin(cfg.global_env))); cfg.buildPlugins && (config.plugins = config.plugins.concat(cfg.buildPlugins)); cfg.res_extra && (cfg.splitChunks ? config.optimization['splitChunks' ] = cfg.splitChunks : config.optimization['splitChunks' ] = splitChunks); module .exports = merge(base, config);
HtmlPlugin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const HtmlPlugin = [];bootFiles.forEach(_path => { const module = _path.split(`./${scan_root} /` )[1 ].split(`/src/${bootName} ` )[0 ]; const chunk = `${module } /index` ; const targetDir = _path.split(`/${bootName} ` )[0 ] const templateType = cfg.templateType ? cfg.templateType : 'html' const htmlConf = Object .assign({ filename : `${chunk} .html` , template : `${targetDir} /index.${templateType} ` , inject : 'body' , chunks : ['vendor' , chunk], hash : true , static_url : cfg.static_url ? cfg.static_url : '' },HtmlPluginConf) ; if (bootList && typeof bootList == 'boolean' ) { entries[chunk] = _path; HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); } else if (Array .isArray(bootList) && bootList.length > 0 ) { bootList.forEach(item => { _bootList.add(item); }); if (_bootList.has(module )) { entries[chunk] = _path; HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); } } }); if (tnsEnabled) { HtmlPlugin.push(new TNSClientPlugin({excludeModules})); }
其他配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const output = cfg.hasHash ? { ...base.output, filename : '[name].[hash].js' , chunkFilename : '[name].[hash].js' , } : base.output; let splitChunks = { cacheGroups : { default : { minChunks : 2 , priority : -20 , reuseExistingChunk : true , }, vendor : { chunks : 'initial' , minChunks : 2 , maxInitialRequests : 5 , minSize : 0 , name : 'vendor' }, commons : { name : 'vendor' , chunks : 'initial' , minChunks : Infinity } } }
6.6 start.config.js与build.config.js差异 6.6.1. start.config.js
使用HotModuleReplacementPlugin热更新模块
使用open-browser-plugin本地起服务时自动打开指定页面
6.6.2. build.cofig.js
使用CleanWebpackPlugin在每一次构建前清除指定目录文件
使用OptimizeCSSAssetsPlugin压缩CSS代码
使用TerserWebpackPlugin压缩JS代码