webpack2歸納總結

本文github倉庫:https://github.com/Rynxiao/webpack2-learn

從v1遷移到v2

1. 配置類型

在webpack1的時候,主要是通過導出單個object來進行配置。例如下面的配置:

// webpack1 導出方式
module.export = {
    entry : 'app.js',
    output : { */... */},
    /* ... */
};

而在webpack2中,則有三種方式來靈活配置,可以針對不同的場景。

1.1 通過不同環境變量導出不同的配置文件

// 可以有兩種方式傳遞當前值,一種是簡單傳遞字符串,另外一種則是傳遞一個對象
// 例如: webpack --env production 控制檯打印的就是 'production',是一個字符串
// 而當這樣調用時:webpack --env.production --env.size 60,控制檯打印的就是 { production : true, size : 60 }

var path = require('path'),
    webpack = require('webpack'),
    UglifyJsPlugin = new webpack.optimize.UglifyJsPlugin(),
    plugins = [];

module.exports = function(env) {

    console.log(env);

    if (env === 'production') {
        plugins.push(UglifyJsPlugin);
    }

    return {
        entry : path.resolve(__dirname, 'js/app.js'),
        output : {
            path : path.resolve(__dirname, 'build'),
            filename : '[name].bundle.js'
        },
        module : {
            rules : [
                { 
                    test : /\.js|\.jsx$/, 
                    loader : 'babel-loader', 
                    options : {
                        presets : ["es2015", "react"]
                    } 
                },
                { 
                    test : /\.css$/,
                    use : ['style-loader', 'css-loader']
                },
                {
                    test : /\.less$/,
                    use : ['style-loader', 'css-loader', 'less-loader']
                }
            ]
        },
        plugins : plugins
    };
}

// 在package.json中配置兩個命令
{
    "dev" : "webpack",
    "build" : "webpack --env production"
}

具體的生產環境構建方式可以參看官網production

1.2 通過promise方式導出配置文件

這種方式的應用場景是在某些情況下,我們暫時拿不到配置文件所需要的配置參數,比如需要配置的文件名等等,或許這是一個異步的操作,通過promise方式可以使我們在異步操作之後得到配置變量,然後再執行配置文件。

// 在這種情況下,1秒之後會返回配置文件並且執行

var path = require('path');

module.exports = () => {
    return new Promise((resolve, reject) => {
        console.log('loading configuration ...');
        setTimeout(() => {
            console.log('loading completed!');
            resolve({
                entry : path.resolve(__dirname, 'js/app.js'),
                output : {
                    path : path.resolve(__dirname, 'build'),
                    filename : '[name].bundle.js'
                },
                module : {
                    rules : [
                        { 
                            test : /\.js|\.jsx$/, 
                            loader : 'babel-loader', 
                            options : {
                                presets : ["es2015", "react"]
                            } 
                        },
                        { 
                            test : /\.css$/,
                            use : ['style-loader', 'css-loader']
                        },
                        {
                            test : /\.less$/,
                            use : ['style-loader', 'css-loader', 'less-loader']
                        }
                    ]
                },
            });
        }, 1000);
    });
}

1.3 同時打包多份配置文件

webpack1時只能導出單份配置文件,在webpack2中可以同時打包多份配置文件,意味着可以爲多個入口文件打包,在多頁面打包的時候,就再也不需要爲在每一個單獨的頁面執行打包命令了。

// config-amd.js
var path = require('path');

module.exports = {
    entry : path.resolve(__dirname, 'js/app.js'),
    output : {
        path : path.resolve(__dirname, 'build'),
        filename : '[name].amd.js',
        libraryTarget : 'amd'
    },
    module : {
        rules : [
            { 
                test : /\.js|\.jsx$/, 
                loader : 'babel-loader', 
                options : {
                    presets : ["es2015", "react"]
                } 
            },
            { 
                test : /\.css$/,
                use : ['style-loader', 'css-loader']
            },
            {
                test : /\.less$/,
                use : ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    }
};

// config-commonjs.js
var path = require('path');

module.exports = {
    entry : path.resolve(__dirname, 'js/app.js'),
    output : {
        path : path.resolve(__dirname, 'build'),
        filename : '[name].commonjs.js',
        libraryTarget : 'commonjs'
    },
    module : {
        rules : [
            { 
                test : /\.js|\.jsx$/, 
                loader : 'babel-loader', 
                options : {
                    presets : ["es2015", "react"]
                } 
            },
            { 
                test : /\.css$/,
                use : ['style-loader', 'css-loader']
            },
            {
                test : /\.less$/,
                use : ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    }
};

// webpack.config.js
var configAmd = require('./config-amd.js'),
    configCommonjs = require('./config-commonjs.js');

module.exports = [
    configAmd,
    configCommonjs
]

2. resolve相關

2.1 extensions 後綴擴展

在webpack2中,不需要默認寫一個空字符串,如果沒有配置這個選項,則默認的後綴名是['.js', '.json'],這樣可以在需要用到import 'some.js'的時候直接寫import 'some'就好。

如果不想開啓自動後綴,則需要在resolve中配置enforceExtension : true,例如:

var path = require('path');

module.exports = {
    entry : // ....,
    // ...
    resolve : {
        enforceExtension : true
    }
};

此時,如果在js/app.js中引用js/text.js,就會報錯

// Error
import './text';

// Right
import './text.js';

2.2 root/fallback/modulesDirectories 文件定位

webapck1 resolve中配置這三個屬性,是告訴webpack在引入模塊的時候必須要尋找的文件夾,webpack2中則直接更換成了一個單獨的屬性modules,默認優先搜索node_modules(注意,這是一個相對位置)

// config
resolve: {
    // root : path.join(__dirname, "src")  webpack1方式
    modules : [
        path.join(__dirname, "src"),    // 優先於node_modules/搜索
        "node_modules"
    ]
}

// 修改 js/app.js
// 在js文件夾中,增加一個lodash.js,如果按照上面的配置了modules,則會優先加載我們自己的lodash庫
import '../css/style.less';
import _ from 'lodash';

console.log(_.isObject([1, 2, 3]));
document.getElementById('container').textContent = 'APP';

// js/lodash.js
export default {
    isObject(a) {
        console.log('this is my lodash library!');
        return a && typeof a === 'object';
    }
}

得到的結果如下圖:

resolve-modules

3. module相關

3.1 module.rules替換module.loaders

The old loader configuration was superseded by a more powerful rules system, which allows configuration of loaders and more. For compatibility reasons, the old module.loaders syntax is still valid and the old names are parsed. The new naming conventions are easier to understand and are a good reason to upgrade the configuration to using module.rules.

大意就是新的命名更容易理解(反正對於我來說就是換了個英文單詞:-D),同時還會兼容老的方式,也就是說,你照樣寫module.loaders還是可以的。

module : {
    // webpack1 way
    // loaders : [...]

    // now
    rules : [
        ...
    ]
}

3.2 module[*].loader寫法

如果需要加載的模塊只需要一個loader,那麼你還是可以直接用loader這個關鍵詞;如果要加載的模塊需要多個loader,那麼你需要使用use這個關鍵詞,在每個loader中都可以配置參數。代碼如下:

module : {
    rules : [
        { test : /\.js|\.jsx$/, loader : 'babel-loader' },

        /* 如果後面有參數需要傳遞到當前的loader,則在後面繼續加上options關鍵詞,例如:
          { 
            test : /\.js|\.jsx$/, 
            loader : 'babel-loader', 
            options : { presets : [ 'es2015', 'react' ] } 
          }
        */

        {
            test : /\.css$/,
            // webpack1 way
            // loader : 'style!css'

            use : [ 'style-loader', 'css-loader' ]
        },
        {
            test : /\.less$/,
            use : [
                'style-loader',     // 默認相當於 { loader : 'style-loader' }
                {
                    loader : 'css-loader',
                    options : {
                        modules : true
                    }
                },
                'less-loader'
            ]
        }
    ]
}

3.2 取消自動添加-loader後綴

之前寫loader通常是這樣的:

loader : 'style!css!less'
// equals to
loader : 'style-loader!css-loader!less-loader'

都自動添加了-loader後綴,在webpack2中不再自動添加,如果需要保持和webpack1相同的方式,可以在配置中添加一個屬性,如下:

module.exports = {
    ...
    resolveLoader : {
        moduleExtensions : ["-loader"]
    }
}

// 然後就可以繼續這樣寫,但是官方並推薦這樣寫
// 不推薦的原因主要就是爲了照顧新手,直接寫會讓剛接觸的童鞋感到困惑
// github.com/webpack/webpack/issues/2986
use : [ 'style', 'css', 'less' ]

3.3 json-loader內置啦

如果要加載json文件的童鞋再也不需要配置json-loader了,因爲webpack2已經內置了。

4. plugins相關

4.1 UglifyJsPlugin 代碼壓縮插件

壓縮插件中的warningssourceMap不再默認爲true,如果要開啓,可以這樣配置

plugins : [
    new UglifyJsPlugin({
        souceMap : true,
        warnings : true
    })
]

4.2 ExtractTextWebapckPlugin 文本提取插件

主要是寫法上的變動,要和webpack2配合使用的話,需要使用version 2版本

// webpack1 way
modules : {
    loaders : [
        { 
            test : /\.css$/, 
            loader : ExtractTextPlugin.extract('style-loader', 'css-loader', { publicPath : '/dist' })
        }   
    ]
},
plugins : [
    new ExtractTextPlugin('bunlde.css', { allChunks : true, disable : false })
]

// webapck2 way
modules : {
    rules : [
        { 
            test : /\.css$/, 
            use : ExtractTextPlugin.extract({
                fallback : 'style-loader',
                use : 'css-loader',
                publicPath : '/dist'
            })
        }
    ]
},
plugins : [
    new ExtractTextPlugin({
        filename : 'bundle.css',
        disable : false,
        allChunks : true
    })
]

5. loaders的debug模式

在webpack1中要開啓loaders的調試模式,需要加載debug選項,在webpack2中不再使用,在webpack3或者之後會被刪除。如果你想繼續使用,那麼請使用以下寫法:

// webpack1 way
debug : true

// webapck2 way 
// webapck2將loader調試移到了一個插件中
plugins : [
    new webpack.LoaderOptionsPlugin({
        debug : true
    })
]

6. 按需加載方式更改

6.1 import()方式

在webpack1中,如果要按需加載一個模塊,可以使用require.ensure([], callback)方式,在webpack2中,ES2015 loader定義了一個import()方法來代替之前的寫法,這個方法會返回一個promise.

// 在js目錄中新增一個main.js
// js/main.js
console.log('main.js');

// webpack1 way
require.ensure([], function(require) {
    var _ = require('./lodash').default;
    console.log(_);
    console.log('require ensure');
    console.log(_.isObject(1));
});

// webpack2 way
// 採用這種方式,需要promise 的 polyfill
// 兩種方式:
// 1. npm install es6-promise --save-dev
//    require('es6-promise').polyfill();
//
// 2. babel方式,在webpack中配置babel插件
//    npm install babel-syntax-dynamic-import --save-dev
//    options : {
//        presets : ['es2015'],
//        plugins : ['syntax-dynamic-import']
//    }
import('./lodash').then(module => {
    let _ = module.default;
    console.log(_);
    console.log('require ensure');
    console.log(_.isObject(1));
});

會得到的chunk文件,如下圖:

code-splitting

code-splitting-console

6.2 動態表達式

可以動態的傳遞參數來加載你需要的模塊,例如:

function route(path, query) {
    return import(`./routes/${ path }/route`)
        .then(route => { ... })
}

7. 熱替換更加簡單

webpack2中提供了一種更簡單的使用熱替換功能的方法。當然如果要用node啓動熱替換功能,依然可以按照webpack1中的方式。

npm install webpack-dev-server --save-dev

// webpack.config.js
module.exports = {
    // ...,
    devServer : {
        contentBase : path.join(__dirname, 'build'),
        hot : true,
        compress : true,
        port : 8080,
        publicPath : '/build/'
    },
    plugins : [
        new webpack.HotModuleReplacementPlugin()
    ]
}

談談V2版本

主要是介紹之前在webpack1中忽略的以及v2版本中新加的一些東西。

1. caching(緩存)

瀏覽器爲了不重複加載相同的資源,因此加入了緩存功能。通常如果請求的文件名沒有變的話,瀏覽器就認爲你請求了相同的資源,因此加載的文件就是從緩存裏面拿取的,這樣就會造成一個問題,實際上確實你的文件內容變了,但是文件名沒有變化,這樣還是從緩存中加載文件的話,就出事了。

那麼,之前傳統的做法就是給每個文件打上加上版本號,例如這樣:

app.js?version=1
app.css?version=1

每次變動的時候就給當前的版本號加1,但是如果每次只有一個文件內容變化就要更新所有的版本號,那麼沒有改變的文件對於瀏覽器來說,緩存就失效了,需要重新加載,這樣就很浪費了。那麼,結合數據摘要算法,版本號根據文件內容生成,那麼現在的版本可能是這樣的。

// before
app.js?version=0add34
app.css?version=1ef4a2

// after
// change app.js content
app.js?versoin=2eda1c
app.css?version=1ef4a2

關於怎麼部署前端代碼,可以查看大公司怎樣開發和部署前端代碼

webpack爲我們提供了更簡單的方式,爲每個文件生成唯一的哈希值。爲了找到對應的入口文件對應的版本號,我們需要獲取統計信息,例如這樣的:

{
  "main.js": "main.facdf96690cca2fec8d1.js",
  "vendor.js": "vendor.f4ba2179a28531d3cec5.js"
}

同時,我們結合html-webpack-plugin使用的話,就不需要這麼麻煩,他會自動給文件帶上對應的版本。具體看法參看之前寫的webpack1知識梳理,那麼我們現在的配置變成了這個樣子:

npm install webpack-manifest-plugin --save-dev

// webpack.config.js
module.exports = {
    entry : { /* ... */ },
    output : {
        path : path.resolve(__dirname, 'build-init'),
        filename : '[name].[chunkhash].js',
        chunkFilename : '[name].[chunkhash].js'
    },
    module : {
        // ...
    },
    plugins : [
        new htmlWebpackPlugin({
            title : 'webpack caching'
        }),
        new WebpackManifestPlugin()
    ]
}

html引入情況

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack caching</title>
  </head>
  <body>
  <div id="container"></div>
  <script type="text/javascript" src="main.facdf96690cca2fec8d1.js"></script><script type="text/javascript" src="vendor.f4ba2179a28531d3cec5.js"></script></body>
</html>

WARNNING:

不要在開發環境下使用[chunkhash],因爲這會增加編譯時間。將開發和生產模式的配置分開,並在開發模式中使用[name].js的文件名,在生產模式中使用[name].[chunkhash].js文件名。

爲了使文件更小化,webpack使用標識符而不是模塊名稱,在編譯的時候會生成一個名字爲manifest的chunk塊,並且會被放入到entry中。那麼當我們更新了部分內容的時候,由於hash值得變化,會引起manifest塊文件重新生成,這樣就達不到長期緩存的目的了。webpack提供了一個插件ChunkManifestWebpackPlugin,它會將manifest映射提取到一個單獨的json文件中,這樣在manifest塊中只需要引用而不需要重新生成,所以最終的配置是這樣的:

var path = require('path'),
    webpack = require('webpack'),
    htmlWebpackPlugin = require('html-webpack-plugin'),
    ChunkManifestWebpackPlugin = require('chunk-manifest-webpack-plugin'),
    WebpackChunkHash = require('webpack-chunk-hash');

module.exports = {
    entry : {
        main : path.resolve(__dirname, 'js/app.js'),
        vendor : path.resolve(__dirname, 'js/vendor.js')
    },
    output : {
        path : path.resolve(__dirname, 'build'),
        filename : '[name].[chunkhash].js',
        chunkFilename : '[name].[chunkhash].js'
    },
    module : {
        // ...
    },
    plugins : [
        new webpack.optimize.CommonsChunkPlugin({
            name : ['vendor', 'manifest'],
            minChunks : Infinity
        }),
        new webpack.HashedModuleIdsPlugin(),
        new WebpackChunkHash(),
        new htmlWebpackPlugin({
            title : 'webpack caching'
        }),
        new ChunkManifestWebpackPlugin({
            filename : 'chunk-mainfest.json',
            manifestVariable : 'webpackManifest',
            inlineManifest : true
        })
    ]
}

tips:如果還不是很明白,去對比一下加了ChunkManifestWebpackPlugin和沒加的區別就可以清楚的感受到了。在本文的代碼文件夾caching中可以看到這一差別

發佈了60 篇原創文章 · 獲贊 29 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章