手记

快速入门webpack(4)- 一些技巧

5. webpack常用技巧

5.1 代码块划分


5.1.1 Commonjs采用require.ensure来产生chunk

require.ensure(dependencies, callback);

//static imports
import _ from 'lodash'

// dynamic imports
require.ensure([], function(require) {
  let contacts = require('./contacts')
})

这一点在output.chunkFileName中已经做过演示,可以去查看


5.1.2 AMD采用require来产生chunk

require(["module-a", "module-b"], function(a, b) {
    // ...
});

5.1.3 将项目APP代码与公共库文件单独打包

我们在basic/app.js中添加如下代码

var $ = require('juqery'),
    _ = require('underscore');

//.....

然后我们在配置文件中添加vendor,以及运用代码分离的插件对生成的vendor块重新命名

var webpack = require("webpack");

module.exports = {
    entry: {
        app: "./app.js",
        vendor: ["jquery", "underscore", ...],
    },
    output: {
        filename: "bundle.js"
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
    ]
};

运行配置文件,效果如下:


5.1.4 抽取多入口文件的公共部分

我们重新建立一个文件夹叫做common,有如下文件:

// common/app1.js

console.log("APP1");
// common/app2.js

console.log("APP2");

打包之后生成的app1.bundle.jsapp2.bundle.js中会存在许多公共代码,我们可以将它提取出来。

// common/webpack.config.js

/**
 * webpack打包配置文件
 * 抽取公共部分js
 */

var webpack = require('webpack');

module.exports = {
    entry : {
        app1 : './app1.js',
        app2 : './app2.js'
    },
    output : {
        path : './assets/',
        filename : '[name].bundle.js'
    },
    module : {
        loaders : [
            { test : /\.js$/, loader : 'babel' },
            { test : /\.css$/, loader : 'style!css' }
        ]
    },
    plugins : [
        new webpack.optimize.CommonsChunkPlugin("common.js")
    ]
};

抽取出的公共js为common.js,如图

查看app1.bundle.js,发现打包的内容基本是我们在模块中所写的代码,公共部分已经被提出到common.js中去了

5.1.5 抽取css文件,打包成css bundle

默认情况下以require('style.css')情况下导入样式文件,会直接在index.html<head>中生成<style>标签,属于内联。如果我们想将这些css文件提取出来,可以按照下面的配置去做。

// extract-css/app1.js
require('./app1.css');
document.getElementById("container").textContent = "APP";

// extract-css/app2.js
require('./app2.css');
document.getElementById("container").textContent = "APP1 APP2";

// extract-css/app1.css
* {
    margin: 0;
    padding: 0;
}
#container {
    margin: 50px auto;
    width: 50%;
    height: 200px;
    line-height: 200px;
    border-radius: 5px;
    box-shadow: 0 0 .5em #000;
    text-align: center;
    font-size: 40px;
    font-weight: bold;
}

// extract-css/app2.css
#container {
    background-color: #f0f0f0;
}

// extract-css/webpack.config.js
/**
 * webpack打包配置文件
 * 抽取公共样式(没有chunk)
 */

var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry : {
        app1 : './app1.js',
        app2 : './app1.js'
    },
    output : {
        path : './assets/',
        filename : '[name].bundle.js'
    },
    module : {
        loaders : [
            { test : /\.js$/, loader : 'babel' },
            { test : /\.css$/, loader : ExtractTextPlugin.extract("style-loader", "css-loader") }
        ]
    },
    plugins : [
        new ExtractTextPlugin("[name].css")
    ]
};

得到的效果如下图:

如果包含chunk文件,并且chunk文件中也因为了样式文件,那么样式文件会嵌入到js中

css合并到一个文件

// ...
module.exports = {
    // ...
    plugins: [
        new ExtractTextPlugin("style.css", {
            allChunks: true
        })
    ]
}

效果如图:

如果包含chunk文件,并且chunk文件中也因为了样式文件,样式文件不会嵌入到js中,而是直接输出到style.css

配合CommonsChunkPlugin一起使用

// ...
module.exports = {
    // ...
    plugins: [
        new webpack.optimize.CommonsChunkPlugin("commons", "commons.js"),
        new ExtractTextPlugin("[name].css")
    ]
}

效果图如下:


5.2 如何给文件打版本

线上发布时为了防止浏览器缓存静态资源而改变文件版本,这里提供两种做法:

5.2.1 使用HtmlWebpackPlugin插件

// version/webpack.config.js

/**
 * webpack打包配置文件
 * 文件打版本,线上发布
 */

var path = require('path');
var HtmlWebpackPlugin =  require('html-webpack-plugin');

module.exports = {
    entry : './app.js',
    output : {
        path : './assets/',
        filename : '[name].[hash].bundle.js',
        publicPath : 'http://rynxiao.com/assets/'
    },
    module : {
        loaders : [
            { test : /\.js$/, loader : 'babel' },
            { test : /\.css$/, loader : 'style!css' }
        ]
    },
    plugins : [
        new HtmlWebpackPlugin({
            filename: './index-release.html',
            template: path.resolve('index.template'),
            inject: 'body'
        })
    ]
};

生成的效果如下:

每次打包之后都会生成文件hash,这样就做到了版本控制


5.2.2 自定义插件给文件添加版本

// version/webpack.config.version.js

/**
 * webpack打包配置文件
 * 文件打版本,线上发布,自定义插件方式
 */

var path = require('path');
var fs = require('fs');
var cheerio = require('cheerio');

module.exports = {
    entry : './app.js',
    output : {
        path : './assets/',
        filename : '[name].[hash].bundle.js',
        publicPath : 'http://rynxiao.com/assets/'
    },
    module : {
        loaders : [
            { test : /\.js$/, loader : 'babel' },
            { test : /\.css$/, loader : 'style!css' }
        ]
    },
    plugins : [
        function() {
            this.plugin("done", function(stats) {
                fs.writeFileSync(
                    path.join(__dirname, "stats.json"),
                    JSON.stringify(stats.toJson())
                );
                fs.readFile('./index.html', function(err, data) {
                    var $ = cheerio.load(data.toString());
                   $('script[src*=assets]').attr('src','http://rynxiao.com/assets/main.' 
                            + stats.hash +'.bundle.js');
                    fs.writeFile('./index.html', $.html(), function(err) {
                        !err && console.log('Set has success: '+ stats.hash)
                    })
                })
            });
        }
    ]
};

效果如图:

可以达到同样的效果,但是stats暂时只能拿到hash值,因为我们只能考虑在hash上做版本控制,比如我们可以建hash目录等等


5.3 shim

比如有如下场景:我们用到 Pen 这个模块, 这个模块对依赖一个 window.jQuery, 可我手头的 jQuery 是 CommonJS 语法的,而 Pen 对象又是生成好了绑在全局的, 可是我又需要通过 require('pen') 获取变量。 最终的写法就是做 Shim 处理直接提供支持:

做法一:

{test: require.resolve('jquery'), loader: 'expose?jQuery'}, // 输出jQuery到全局
{test: require.resolve('pen'), loader: 'exports?window.Pen'}    // 将Pen作为一个模块引入

做法二:

new webpack.ProvidePlugin({
    $: "jquery",
    jQuery: "jquery",
    "window.jQuery": "jquery"
})

This plugin makes a module available as variable in every module.
The module is required only if you use the variable.
Example: Make $ and jQuery available in every module without writing require("jquery").


5.4 怎样写一个loader

Loader 是支持链式执行的,如处理 sass 文件的 loader,可以由 sass-loader、css-loader、style-loader 组成,由 compiler 对其由右向左执行,第一个 Loader 将会拿到需处理的原内容,上一个 Loader 处理后的结果回传给下一个接着处理,最后的 Loader 将处理后的结果以 String 或 Buffer 的形式返回给 compiler。固然也是希望每个 loader 只做该做的事,纯粹的事,而不希望一箩筐的功能都集成到一个 Loader 中。

官网给出了两种写法:

// Identity loader
module.exports = function(source) {
  return source;
};
// Identity loader with SourceMap support
module.exports = function(source, map) {
  this.callback(null, source, map);
};

第一种为基础的写法,采用return返回, 是因为是同步类的 Loader 且返回的内容唯一。如果你写loader有依赖的话,同样的你也可以在头部进行引用,比如:

// Module dependencies.
var fs = require("fs");
module.exports = function(source) {
  return source;
};

而第二种则是希望多个loader之间链式调用,将上一个loader返回的结果传递给下一个loader

案例

比如我想开发一个es6-loader,专门用来做以.es6文件名结尾的文件处理,那么我们可以这么写

// loader/es6-loader.js
// 当然如果我这里不想将这个loader所返回的东西传递给下一个laoder,那么我
// 可以在最后直接返回return source
// 这里改变之后,我直接可以扔给babel-loader进行处理
module.exports = function(source, map) {
    // 接收es6结尾文件,进行source改变
    source = "console.log('I changed in loader');"
    // 打印传递进来的参数
    console.log("param", this.query);
    // ... 我们还可以做一些其他的逻辑处理
    this.callback(null, source, map);
};

// loader/loader1.es6
let a = 1;
console.log(a);

// loader/app.js
// 向loader中传递参数
require('./es6-loader?param1=p1!./loader1.es6');
document.getElementById("container").textContent = "APP";

执行webpack打包命令,在控制台会打印出param的值,如图:

在执行完成之后,打开index.html,在控制台打印出“I changed in loader”,而不是1

进阶

可以去阅读以下这篇文章 如何开发一个 Webpack loader


5.4 怎样写一个plugin

插件基本的结构

插件是可以实例化的对象,在它的prototype上必须绑定一个apply方法。这个方法会在插件安装的时候被Webpack compiler进行调用。

function HelloWorldPlugin(options) {
    // Setup the plugin instance with options...
}

HelloWorldPlugin.prototype.apply = function(compiler) {
    compiler.plugin('done', function() {
        console.log('Hello World!'); 
    });
};

module.exports = HelloWorldPlugin;

安装一个插件,将其添加到配置中的plugins数组中。

var HelloWorldPlugin = require('hello-world');

var webpackConfig = {
// ... config settings here ...
    plugins: [
        new HelloWorldPlugin({options: true})
    ]
};

执行效果如图:

这里只作简单的引入,平常一般都不需要自己写插件,如果想进一步了解,可以去看官网例子

5.5 布置一个本地服务器

// 1.全局安装webpack-dev-server
cnpm install -g webpack-dev-server

// 2. 设置一个文件启动目录,运行
webpack-dev-server --content-base basic/

// 3. 在浏览器输入localhost:8080

5.6 热替换

// auto-refresh/app.js
document.getElementById("container").textContent = "APP APP HOT ";
console.log("OK");

// auto-refresh/server.js
var webpack = require('webpack');
var config = require('./webpack.config.js');
var WebpackDevServer = require("webpack-dev-server");

var compiler = webpack(config);
new WebpackDevServer(webpack(config), {
    publicPath: config.output.publicPath,
    hot: true,
    noInfo: false,
    historyApiFallback: true
}).listen(8080, 'localhost', function (err, result) {
    if (err) {
        console.log(err);
    }
    console.log('Listening at localhost:3000');
});

// auto-refresh/webpack.config.js
/**
 * webpack打包配置文件
 */

var webpack = require('webpack');

module.exports = {
    entry : [
        'webpack-dev-server/client?http://127.0.0.1:8080', // WebpackDevServer host and port
        'webpack/hot/only-dev-server',
        './app.js'
    ],
    output : {
        path : './assets/',
        filename : '[name].bundle.js',
        publicPath : './assets/'
    },
    module : {
        loaders : [
            { test : /\.js$/, loader : 'react-hot!babel' },
            { test : /\.css$/, loader : 'style!css' }
        ]
    },
    plugins : [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': '"development"'
        }),
    ]
};

// auto-refresh/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>basic webpack</title>
</head>
<body>
    <div id="container"></div>
    <script src="./assets/main.bundle.js"></script>
</body>
</html>

// 运行
node server.js

// 浏览器输入:localhost:8080

5.7 让wepack.config.js支持es6写法

// 1. 安装babel-core、babel-preset-es2015以及babel-loader

// 2. 项目根目录下配置.babelrc文件
{
  "presets": ["es2015"]
}

// 3. 将webpack.config.js重新命名为webpack.config.babel.js

// 4.运行webpack --config webpack.config.babel.js

// 说明node 版本5.0以上,babel-core版本6以上需要如此配置

这是一个 Webpack 支持,但文档里完全没有提到的特性 (应该马上就会加上)。只要你把配置文件命名成 webpack.config.[loader].js ,Webpack 就会用相应的 loader 去转换一遍配置文件。所以要使用这个方法,你需要安装 babel-loader 和 babel-core 两个包。记住你不需要完整的 babel 包。

其他办法(未成功)

1.在上述的方案中,其实不需要重新命名就可以直接运行webpack,但是今天试了一直不成功
2.{ 
    test : /\.jsjsx$/, 
    loader : 'babel',
    query: {
          //添加两个presents 使用这两种presets处理js或者jsx文件
          presets: ['es2015', 'react']
    } 
}
7人推荐
随时随地看视频
慕课网APP