Fis3遷移至Webpack實戰

Webpack從2015年9月第一個版本橫空初始至今已逾2載。它的出現,顛覆了一大批主流構建如Ant、Grunt和Gulp等等。騰訊NOW直播IVWEB團隊以前一直採用Fis構建,本篇文章主要介紹從Fis遷移到webpack遇到的問題和背後的黑科技,內容包括inline-resource、多頁面構建、資源壓縮、文件hash、文件目錄規則等等。javascript

爲何要遷移至webpack?

有兩個層面的緣由:css

  • 首先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的寫法兼容各個操做系統。html

資源內聯 (inline-resource)

inline-resource的好處是能夠減小css,js等的請求數,同時html加載的時候便可同時加載了這些內聯的css、js等靜態資源,能夠有效的減小白屏或者頁面閃動的問題。前端

這裏的內聯分爲2種,一種是靜態的html片斷,css,js等,這些資源一開始就存在項目的某個目錄下;另外一種是構建過程當中動態生成的css,js文件。html5

對於html的內聯,寫法以下:java

${require('raw-loader!../src/assets/inline/meta.html')}

對於js的內聯,須要增長babel-loader將ES6的語法進行轉換,避免瀏覽器直接解析致使報錯。寫法以下:node

<script>${require('raw-loader!babel-loader!../src/node_modules/@tencent/report-whitelist/lib/index.js')}</script>

說明:不能將html-loader和html-webpack-plugin同時使用,html-loader會致使默認的ejs模板引擎語法解析實效,形成 ${} 和 <% = %>等語法不生效linux

上面講述瞭如何內聯靜態的資源文件,那麼如何內聯構建過程當中動態生成的資源文件呢?這裏須要藉助html-webpack-inline-source-plugin來加強html-webpack-plugin的功能。好比:將構建過程當中生成的css文件inline到html模板裏面去。webpack

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模板文件中了。git

多頁面構建

多頁面構建,或者稱爲通配(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的打包優化將會用另一篇文章介紹,敬請期待~

參考文檔

打個廣告

相關文章
相關標籤/搜索