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.js
、app2.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']
}
}