Fis3迁移至Webpack实战
Webpack从2015年9月第一个版本横空初始至今已逾2载。它的出现,颠覆了一大批主流构建如Ant、Grunt和Gulp等等。腾讯NOW直播IVWEB团队之前一直采用Fis构建,本篇文章主要介绍从Fis迁移到webpack遇到的问题和背后的黑科技,内容包括inline-resource、多页面构建、资源压缩、文件hash、文件目录规则等等。
为什么要迁移至webpack?
有两个层面的原因:
- 首先webpack的社区生态火爆,插件齐全并且维护更新的很频繁,遇到了问题,比较容易解决。
- webpack里面有happypack多实例构建方案、code spliting按需加载文件等方案, 可以有效的进行打包构建持续优化, 这些在Fis里面是缺少的。
区分构建的开发or生产环境?
"scripts": {
"dev": "cross-env NODE_ENV=dev nodemon --watch webpack.config.js --exec \"webpack-dev-server --config webpack.config.js --env development\" --progress --colors",
"build": "webpack --config webpack.config.js --env production --progress --colors",
...
},
通过在package.json中注入环境变量的方式,注入NODE_ENV=dev代表开发环境,默认为生产环境。这里使用cross-env的原因是:windows下 在package.json中直接使用 NODE_ENV=dev 不生效,需写成 set NODE_ENV=dev,cross-env的写法兼容各个操作系统。
资源内联 (inline-resource)
inline-resource的好处是可以减少css,js等的请求数,同时html加载的时候即可同时加载了这些内联的css、js等静态资源,可以有效的减少白屏或者页面闪动的问题。
这里的内联分为2种,一种是静态的html片段,css,js等,这些资源一开始就存在项目的某个目录下;另一种是构建过程中动态生成的css,js文件。
对于html的内联,写法如下:
${require('raw-loader!../src/assets/inline/meta.html')}
对于js的内联,需要增加babel-loader将ES6的语法进行转换,避免浏览器直接解析导致报错。写法如下:
<script>${require('raw-loader!babel-loader!../src/node_modules/@tencent/report-whitelist/lib/index.js')}</script>
说明:不能将html-loader和html-webpack-plugin同时使用,html-loader会导致默认的ejs模板引擎语法解析实效,造成 ${} 和 <% = %>等语法不生效
上面讲述了如何内联静态的资源文件,那么如何内联构建过程中动态生成的资源文件呢?这里需要借助html-webpack-inline-source-plugin来增强html-webpack-plugin的功能。比如:将构建过程中生成的css文件inline到html模板里面去。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
new HtmlWebpackPlugin({
inlineSource: isDev ? undefined : '\\.css$',
template: __dirname + '/template/index.tmpl.html',
filename: 'activity.html',
inject: true,
}),
new HtmlWebpackInlineSourcePlugin(),
...
上面这段代码,html-webpack-plugin本身并不具备inlineSource的属性。引入了html-webpack-inline-source-plugin之后,就可以通过inlineSource属性来匹配哪些文件需要动态的内联进html模板文件中了。
多页面构建
多页面构建,或者称为通配(wildcards)构建。即需要构建的页面数量是不确定的,可能A业务有3张页面,B业务有5张页面。因此,我们不能把entry写死了:
entry: {
activity: './src/pages/activity/init.js', // 深海寻宝活动首页
my-reward: './src/pages/my-reward/init.js', // 我的奖励
exchange: './src/pages/exchange/init.js' // 线下兑换奖品
},
为什么上面的写法不可取呢?很明显:上面的写法把entry写死了,这并不通用。后面如果产品需求发生改变,需要新增一张页面,就需要手动修改构建脚本。我们需要的entry是:'./src/pages/**/init.js',它能够像一些linux的命令,具备匹配某个规则的所有结果的能力。这里的思路是借助glob,达到动态entry的目的。
entry: glob.sync('./src/pages/**/init.js'),
在webpack构建中,一个页面需要一个与之对应的HtmlWebpackPlugin实例,N个页面需要N个与之对应的HtmlWebpackPlugin。此处需要动态的设置HtmlWebpackPlugin的实例个数。
const newEntry = {};
Object.keys(config.entry).map((index) => {
const entry = config.entry[index];
const match = entry.match(/\/pages\/(.*)\/init.js/);
const pageName = match && match[1];
newEntry[pageName] = entry;
config.plugins.push(
new HtmlWebpackPlugin({
inlineSource: isDev ? undefined: '\\.css$',
template: __dirname + '/template/index.tmpl.html',
filename: `${pageName}.html`,
chunks: [pageName],
inject: true
})
);
});
config.entry = newEntry;
html、css和js压缩
对于html文件里面的内容压缩可以通过给html-webpack-plugin设置minify参数,html-webpack-plugin的压缩配置其实是直接集成了 html-minifier,因此minify的参数设置可以直接参考html-minifier的文档。
config.plugins.push(
new HtmlWebpackPlugin({
inlineSource: isDev ? undefined: '\\.css$',
template: __dirname + '/template/index.tmpl.html',
filename: `${pageName}.html`,
chunks: [pageName],
inject: true,
minify: {
minifyJS: true, // 仅压缩内联在html里面的js
minifyCSS: true, // 仅压缩内联在html里面的css
html5: true, // 以html5的文档格式解析html的模板文件
removeComments: false, // 不删除Html文件里面的注释
collapseWhitespace: true, // 删除空格
preserveLineBreaks: false // 删除换行
}
})
);
设置了上面的minify参数后,看到生成的html文件的内容全部在1行上,需要注意的是:minifyJS和minifyCSS只会压缩内联在这个html文件的css和js内容,对于单独的css文件和js文件并不会压缩。 那么打包出来的css和js文件如何压缩呢?
对于css文件压缩,直接开启css-loader的压缩参数参数minimize为true即可:
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: "css-loader",
options: { // 设置css-loader的minimize参数为true
minimize: true
}
},
{
loader: "sass-loader"
}
]
})
},
css-loader开启压缩可能会报错 Module build failed: BrowserlistError: unkonwn version 61 and _chr,解决办法:
$ npm i caniuse-db —save #更新caniuse-db到最新版本
对于js文件的压缩,可以通过引入 webpack 内置的 UglifyJsPlugin:
const webpack = require('webpack');
plugins: [
...
new webpack.optimize.UglifyJsPlugin(),
...
],
文件Hash
每次功能发布上线,都需要重新构建一次源代码,生成一个新的文件版本列表。此处文件Hash的方式就是一种版本管理的方式,发布时替换有变化的版本的文件,达到增量更新的效果。此处Hash策略是:根据文件内容进行hash,取8位。
JS文件:
output: {
filename: '[name]_[chunkhash:8].js', // 进行js脚本hash
path: path.resolve(__dirname, 'public/'),
publicPath: isDev ? '/' : cdnUrl + '/',
},
Css文件:
plugins: [
new CleanWebpackPlugin(['./public']),
new ExtractTextPlugin('[name]_[contenthash:8].css'), // css文件hash
new webpack.optimize.UglifyJsPlugin(),
...
]
Img文件:
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]', // img文件hash
}
}
},
...
]
多终端适配
开发过程中,不同分辨率的浏览器适配是个让前端开发者头疼的问题。手淘的rem方案完美解决了这个问题,它的核心思想是页面加载时动态设置body的font-size值和rem和px转换的单位。
为了不改变编程习惯,开发过程中仍然使用px单位来作为基础布局长度单位,以750px宽度的视觉稿作为基准,设置rem和px的转换单位为1rem=75px。那么px2rem如何集成进webpack中呢?首先增加css的解析px2rem-loader,然后在html头部引入lib-flexible文件。
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: "css-loader"
},
{
loader: "px2rem-loader", // 增加px2rem-loader,并且设置rem单位为75px
options: {
remUnit: 75
}
},
{
loader: "sass-loader"
}
]
})
},
其它feature
- 开发环境支持WDS: webpack3.x版本自带webpack-dev-server,开发环境中开启WDS。这样依赖的文件发生变化后,会自动增量构建并且刷新浏览器
- 支持HMR: webpack.config.js文件内容变化后,会触发热更新逻辑,此处通过nodemon来守护webpack的构建进程,eg:
"scripts": {
"dev": "cross-env NODE_ENV=dev nodemon --watch webpack.config.js --exec \"webpack-dev-server --config webpack.config.js --env development\" --progress --colors"
...
},
由于篇幅原因,关于webpack的打包优化将会用另外一篇文章介绍,敬请期待~
参考文档
- webpack 官方文档
- 一本介绍webpack比较全面的教程
- html-webpack-plugin文档
- Wildcards in entry points
- BrowserslistError: Unknown version 55 of and_chr
打个广告
- 腾讯NOW直播工程化解决方案feflow正式开源啦~: https://github.com/cpselvis/feflow-cli 集项目初始化、开发构建、代码规范、代码发布 于一身的工具。
Fis3迁移至Webpack实战的更多相关文章
- 30分钟手把手教你学webpack实战
30分钟手把手教你学webpack实战 阅读目录 一:什么是webpack? 他有什么优点? 二:如何安装和配置 三:理解webpack加载器 四:理解less-loader加载器的使用 五:理解ba ...
- webpack实战
webpack实战 30分钟手把手教你学webpack实战 2015-09-08 23:02 by 龙恩0707, 175 阅读, 0 评论, 收藏, 编辑 30分钟手把手教你学webpack实战 阅 ...
- 每天记录一点:NetCore获得配置文件 appsettings.json vue-router页面传值及接收值 详解webpack + vue + node 打造单页面(入门篇) 30分钟手把手教你学webpack实战 vue.js+webpack模块管理及组件开发
每天记录一点:NetCore获得配置文件 appsettings.json 用NetCore做项目如果用EF ORM在网上有很多的配置连接字符串,读取以及使用方法 由于很多朋友用的其他ORM如S ...
- Webpack实战(四):教教你如何轻松搞定-预处理器(loader)
前面三节,我主要给大家分享了有关webpack的一些配置的知识点,如何打包js文件,而如果我们遇到其他类型的资源如图片.css.字体font等等,我们该如何处理呢?今天会介绍预处理器(loader), ...
- Webpack实战(五):轻松读懂Webpack如何分离样式文件
在上一篇文章中我给大家分享了预处理器(loader),里面讲到了style-loader 和css-loader,有关样式引入的问题,但是上面的样式文件只是引入到style标签里面,并不是我想要的样式 ...
- Webpack实战(三):作为前端你不得不懂的Webpack资源入口和出口的配置
关于Webpack前两篇跟大家分享的主要是Webpack的一些基本的配置,今天开始我们详细了解一下有关Webpack的各种配置,今天主要跟大家分享的是Webpack的资源入口和资源出口的配置. 如果想 ...
- 微信应用号开发知识贮备之Webpack实战
天地会珠海分舵注:随着微信应用号的呼之欲出,相信新一轮的APP变革即将发生.作为行业内人士,我们很应该去拥抱这个趋势.这段时间在忙完工作之余准备储备一下这方面的知识点,以免将来被微信应用号的浪潮所淹没 ...
- jenkins+gitlab自动化编译部署方案探索及服务端编译webpack实战
一. 背景 之前我们的开发流程为在本地进行webpack打包编译,然后svn提交源代码和编译后的代码.同时每次提交前也会从svn更新源代码和编译后的代码.这样做有几个缺点: 1. svn 更新和提交编 ...
- HBase数据迁移到Kafka实战
1.概述 在实际的应用场景中,数据存储在HBase集群中,但是由于一些特殊的原因,需要将数据从HBase迁移到Kafka.正常情况下,一般都是源数据到Kafka,再有消费者处理数据,将数据写入HBas ...
随机推荐
- Interface request structure used for socket ioctl's
1. 结构体定义 /* * Interface request structure used for socket * ioctl's. All interface ioctl's must have ...
- SSH/HTTPS安全的本质都依赖于TLS/SSL
1.SSH/HTTPS的安全本质是TLS/SSL. 2.1990年互联网上的网页主要是静态内容,作为信息发布,使用HTTP明文传输是可以的.不过,后来很多公司开始使用网页进行金融交易,例如:股票,于是 ...
- linux安装禅道的步骤
linux一键安装禅道:1.禅道帮助文档:http://www.zentao.net/book/zentaopmshelp/90.html 2.修改Apache的端口号:/opt/zbox/zbox ...
- DOS常用命令及进制转换
DOS是一种用户单任务磁盘操作系统.在DOS中,我们可以通过DOS命令来管理设备和文件,如打印文件.删除文件,复制文件,创建新的文件夹和文档并编写内容等功能同时也是JAVA编程基础的一个入门.进入DO ...
- 学习SVG 重点汇总
什么是SVG? Δ SVG 指可伸缩矢量图形 (Scalable Vector Graphics) Δ SVG 用来定义用于网络的基于矢量的图形 Δ SVG使用XML格式来定义图形 Δ SVG ...
- 解决phpstorm ftp自动保存文件问题
初次使用phpstorm, 1.配置ftp时,远程文件要用/ftp用户名/文件夹名: 2.由于版本管理的原因(猜测),直接从本地原有文件修改时各种办法都无法上传,结果从服务器上下载一份再修改,解决这个 ...
- ABAP开发实用快捷键
在程序中注释代码往往受输入法影响,看了别人的一篇博客,结合自己的测试发现用如下方法可以直接注释源代码不受输入法影响 添加注释:ctrl + space + < 去掉注释:ctrl + space ...
- EF6中使用事务的方法
默认情况当你执行SaveChanges()的时候(insert update delete)来操作数据库时,Entity Framework会把这个操作包装在一个事务里,当操作结束后,事务也结束了. ...
- java多线程开发容易犯的错误
昨天在社区上看到有人讨论多线程使用,多线程遇到一些问题以及一些使用技巧记录一下.为什么要使用多线程, 不能是为了用而用,和设计模式一样用的合理,会让程序更易于理解,用的不合理反而会让程序变得更难理解. ...
- K - Kia's Calculation (贪心)
Kia's Calculation Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others ...