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的打包优化将会用另外一篇文章介绍,敬请期待~

参考文档

打个广告

  • 腾讯NOW直播工程化解决方案feflow正式开源啦~ 集项目初始化、开发构建、代码规范、代码发布 于一身的工具。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 无意中看到zhangwnag大佬分享的webpack教程感觉受益匪浅,特此分享以备自己日后查看,也希望更多的人看到...
    小小字符阅读 8,140评论 7 35
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 12,671评论 7 110
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 webpack介绍和使用 一、webpack介绍 1、由来 ...
    it筱竹阅读 11,028评论 0 21
  • 在现在的前端开发中,前后端分离、模块化开发、版本控制、文件合并与压缩、mock数据等等一些原本后端的思想开始...
    Charlot阅读 5,428评论 1 32
  • 电话亭,是爷爷留下的。 (一)安家 总听爷爷说起,我们的老家在山西,逃荒的时候来的青岛,这里鱼虾蟹多,只要不惜力,...
    邻家安心阅读 505评论 2 5