偶然的一次机会,接触了某大佬的webpack总结,好生喜欢,再次加深了我对webpack的了解,下面就是在一些webpack的个人总结
我们先看下一个webpack.config.js文件
const webpack = require('webpack');
const UglifyJsPlugin=require('uglifyjs-webpack-plugin');
module.exports = {
devtool: 'eval-source-map',
// externals: {
// 'react': 'window.React',
// 'react-dom': 'window.ReactDOM'
// },
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.css$/,
loader: 'style-loader!css-loader' //添加对样式表的处理
}]
},
plugins: [
new UglifyJsPlugin(),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./manifest.json'),
})
],
devServer: {} // Omitted for brevity
}
根据这个文件我们来逐步了解一下webpack
一、生成Source-Map
开发总是离不开调试,如果可以更加方便的调试当然就能提高开发效率,不过打包后的文件有时候不容易找到出错的地方对应的源代码位置,Source Maps就是来解决这个问题的。 在webpack的配置文件中配置source maps,需要配置devtool,有四种配置选项
- source-map: 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包文件的构建速度;
- cheap-module-source-map: 在一个单独的文件中生成一个不带列映射的map,不带列映射提高项目构建速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便
- eval-source-map: 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。不过在开发阶段这是一个非常好的选项,但是在生产阶段一定不要用这个选项
- cheap-module-eval-source-map: 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点;
module.exports = {
devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
}
}
二、使用webpack构建本地服务器
webpack提供一个可选的本地开发服务器,可以检测代码的修改,并自动刷新浏览器的结果。该本地服务器基于node.js构建,在使用前我们需要单独安装作为项目依赖。
npm install --save-dev webpack-dev-server
配置选项:
- contentBase: 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public”目录)
- port: 设置默认监听端口,如果省略,默认为“8080”
- inline: 设置为true,当源文件改变时会自动刷新页面
- historyApiFallback: 在开发单页应用时非常有用,它依赖于HTML5
启动server:
//webpack非全局安装的情况
//linux
./node_modules/.bin/webpack-dev-server
//使用npm
//package.json
"server": "webpack-dev-server"
//运行
npm run server
三、Loaders
使用不同的loader,webpack通过调用外部脚本或者工具可以对不同格式的文件进行处理。 Loaders需要单独安装并且需要在webpack.config.js下的modules关键字下进行配置,Loaders的配置选项包括以下几方面:
- test: 一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
- loader: loader的名称(必须
- include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
query: 为loaders提供额外的设置选项(可选)
安装依赖包:
// npm一次性安装多个依赖模块,模块之间用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader', //在webpack的module部分的loaders里进行配置即可
query: {
presets: ['es2015', 'react']
}
}]
},
devServer: {
contentBase: "./public",
historyApiFallback: true,
inline: true
}
}
因为babel有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 “.babelrc” 的配置文件中
//.babelrc
{
"presets": ["react", "es2015"]
}
四、css module
webpack提供两个工具处理样式表,css-loader 和 style-loader,二者处理的任务不同,css-loader使你能够使用类似@import 和 url(…)的方法实现 require()的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。
//安装
npm install --save-dev style-loader css-loader
//css-loader配置
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'//添加对样式表的处理
}]
},
devServer: {}
}
import './app.css';//使用require导入css文件
五、解决webpack打包慢的问题
1、Exposing global variables(公开全局变量)
如果想使用一些全局变量,但是不想使他们包含在Webpack包里,可以在webpack.config.js中启用external字段。
你也可以将react和react-dom放到外部,这将大大减少bundle.js的建造时间和建筑尺寸。
//data.js
var data='Hello zhaishuang webpack';
//main.jsx
const data=require('data');
const React=require('react');
const ReactDOM=require('react-dom');
ReactDOM.render(
<h1>{data}</h1>,
document.body
)
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>expose global variables</title>
</head>
<body>
<script src="data.js"></script>
<script src="bundle.js"></script>
</body>
</html>
//webpack.config.js
module.exports={
entry:'./main.jsx',
output:{
filename:'bundle.js'
},
module:{
rules:[{
test:/\.js[x]?$/,
exclude:/node_modules/,
use:{
loader:'babel-loader'
}
}]
},
externals:{
'data':'data'
}
}
2、使用webpack.DllPlugin
只对库文件打包一次。也就是说,只要库文件不变,只需要打包一次,以后再打包业务代码和库文件没关系啦,这样一来真正做到了库文件永远是那个库文件,只要库文件不变,缓存永远有效(Etag不变),打起包来把库丢到脑后,神清气爽。介绍一下最简单的使用方式:首先另写一个 webpack 配置文件,毕竟是单独打包库了,假设 webpack.config.dll.js
/*dll.config.js*/
const path = require('path');
const webpack = require('webpack');
const vendors = [
'react',
'react-dom',
// ...其它库
];
module.exports = {
output: {
path: path.resolve(__dirname, './public'),
filename: '[name].js',
library: '[name]',
},
entry: {
"lib": vendors,
},
plugins: [
new webpack.DllPlugin({
path: 'manifest.json',
name: '[name]',
context: __dirname,
}),
],
};
webpack.DllPlugin 的选项中:
- path 是 manifest.json 文件的输出路径,这个文件会用于后续的业务代码打包;
- name 是dll暴露的对象名,要跟 output.library 保持一致;
- context 是解析包路径的上下文,这个要跟接下来配置的webpack.config.js 一致。
//package.json
"dll": "webpack --config ./dll.config.js"
运行:
npm run dll
生成 lib.js 和 manifest,json。
接下来打包webpack.config.js:
const webpack = require('webpack');
module.exports = {
devtool: 'eval-source-map',
// externals: {
// 'react': 'window.React',
// 'react-dom': 'window.ReactDOM'
// },
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}]
},
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./manifest.json'),
})
],
devServer: {} // Omitted for brevity
}
webpack.DllReferencePlugin 的选项中:
- context 需要跟之前保持一致,这个用来指导 Webpack 匹配 manifest 中库的路径
- manifest 用来引入刚才输出的 manifest.json 文件
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>expose global variables</title>
</head>
<body>
<script src="lib.js"></script>
<script src="bundle.js"></script>
</body>
</html>
其实还有一个速度的优化点,就是配置babel,让它排除一些文件,当loader这些文件时不进行转换,自动跳过;可在.babelrc文件中配置,示例:
{
"presets": ['es2015', 'react'],
"ignore":[
"jquery.js",
"jquery.min.js",
"angular.js",
"angular.min.js",
"bootstrap.js",
"bootstrap.min.js"
]
}
六、Plugins
1、UglifyJSPlugin
这个插件使用 UglifyJS 去压缩你的JavaScript代码。除了它从 webpack中解耦之外,它和 webpack 核心插(webpack.optimize.UglifyJSPlugin) 是同一个插件
//webpack.config.js
var webpack=require('webpack');
var UglifyJsPlugin=require('uglifyjs-webpack-plugin');
module.exports={
entry:'./main.js',
output:{
filename:'bundle.js'
},
plugins:[
new UglifyJsPlugin()
]
};
2、html-webpack-plugin&&open-browser-webpack-plugin(打开新的tab页面)
html-webpack-plugin
1.为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
2.可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口
作用:
直接为项目生成一个或多个HTML文件(HTML文件个数由插件实例的个数决定),并将webpack打包后输出的所有脚本文件自动添加到插件生成的HTML文件中。通过配置,可以将根目录下用户自定义的HTML文件作为插件生成HTML文件的模板。另外,还可以通过向插件传递参数,控制HTML文件的输出。
const HtmlwebpackPlugin=require('html-webpack-plugin');
const OpenBrowserPlugin=require('open-browser-webpack-plugin');
module.exports={
entry:'./main.js',
output:{
filename:'bundle.js'
},
plugins:[
new HtmlwebpackPlugin({
title:'html-webpack-plugin',
finename:'index.html'
}),
new OpenBrowserPlugin({
url:'http://localhost:3000'
})
]
}
七、package.json中dependences和devDependencies的区别
devDependencies 是你开发时候用的库, 比如测试库,测试服务器之类的,在真实生产环境是不需要的。
dependencies 是你生产环境需要的依赖库。
如果你使用了一些构建工具,比如webpack之类的,打包的时候,是不会把dev库打进去的。
八、Code splitting
们打包时通常会生成一个大的bundle.js(或者index,看你如何命名)文件,这样所有的模块都会打包到这个bundle.js文件中,最终生成的文件往往比较大。
code splitting就是指将文件分割为块(chunk),webpack使我们可以定义一些分割点(split point),根据这些分割点对文件进行分块,并实现按需加载。第三方类库单独打包。由于第三方类库的内容基本不会改变,可以将其与业务代码分离出来,这样就可以将类库代码缓存在客户端,减少请求。按需加载。webpack支持定义分割点,通过require.ensure进行按需加载。通用模块单独打包。我们代码中可能会有一些通用模块,比如弹窗、分页、通用的方法等等。其他业务代码模块常常会有引用这些通用模块。若按照2中做,则会造成通用模块重复打包。这时可以将通用模块单独打包出来。
//a.js
module.exports='Hello zhaishuang';
//main.js
// Now a.js is requested, it will be bundled into another file
const load=require('bundle-loader!./a.js');
// To wait until a.js is available (and get the exports)
// you need to async wait for it.
load(function(file){
document.open();
document.write('<h1>'+file+'</h1>');
document.close();
})
九、CommonsChunkPlugin
提取公共包,公共包那就是不只一个地方使用喽,单页应用(单入口)的库只有他自己使用,不能算公共包吧?这个插件提取的公共包,每次是会重新打包的(Etag会不同),无论是节约打包时间,(虽然微不足道的时间但毕竟是无用功:库根本没变么),还是对浏览器缓存的利用(万一 max-age 过期了你就放弃缓存了么?)都不是好的方案。最佳方案浮出水面:DllPlugin
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Commonchunk</title>
</head>
<body>
<div id="a"></div>
<div id="b"></div>
<script src="commons.js"></script>
<script src="bundle1.js"></script>
<script src="bundle2.js"></script>
</body>
</html>
//main1.jsx
const React=require('react');
const ReactDOM=require('react-dom');
ReactDOM.render(
<h1>Hello world</h1>,
document.getElementById('a')
)
//main2.jsx
const React=require('react');
const ReactDOM=require('react-dom');
ReactDOM.render(
<h2>Hello shuangshuang</h2>,
document.getElementById('b')
)
//webpack.config.js
const webpack=require('webpack');
module.exports={
entry:{
bundle1:'./main1.jsx',
bundle2:'./main2.jsx'
},
output:{
filename:'[name].js'
},
module:{
rules:[
{
test:/\.js[x]?$/,
exclude:/node_modules/,
use:{
loader:'babel-loader'
}
}
]
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name:'commons',
filename:"commons.js"
})
]
}
十、Vendor chunk
当多个JS文件拥有公共块时,可以使用插件CommonsChunkPlugin将公共部分提取到一个单独的文件中,这有助于浏览器的缓存和节省带宽。您还可以将脚本中的第三方库从CommonsChunkPlugin中提取到单独的文件中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vendor chunk</title>
</head>
<body>
<h1></h1>
<script src="vendor.js"></script>
<script src="bundle.js"></script>
</body>
</html>
// main.js
$('h1').text('Hello World');
//webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: {
app: './main.js'
},
output: {
filename: 'bundle.js'
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
};
如果你想在每个模块中使用一个模块作为全局变量,
比如在没有写入require(“jquery”)的情况下在每个模块中都可以使用$和jQuery。 你应该使用ProvidePlugin,它可以自动加载模块,而不必在任何地方导入或需要它们。