前端将大型项目分成一个个单独的模块,一般封装好的每个模块都会实现一个目的明确的完成的功能。如何处理这些模块以及模块之间的依赖,将这些模块打包在一起成为一个完整的应用,就是webpack的任务。webpack一般从entry文件开始,逐步的搜索项目的所有依赖,通过各种loader(比如css-loader、url-loader、babel-loader)以及各种插件(如commonChunksPlugin、extractTextPlugin、UglifyJsWebpackPlugin、HtmlWebpackPlugin、InlineManifestWebpackPlugin、DefinePlugin、hashedModulesPlugin、OptimizeCssAssetsPlugin)处理优化资源文件,最终打包出一个或者多个适合在浏览器中运行的js文件
基本知识
- entry
- output
- externals
- loaders
- plugins
- resolve
- configuration
- modules
- modules resolution
- targets
- manifest
- code split
- Caching
开发和发布环境配置
- 通用配置
- 公共插件
- webpack-merge
- HtmlWebpackPlugin
- DefinePlugin
- 公共插件
- 开发环境
- 使用source map
- 配置一个实现热更新的localhost的服务器
- 插件
- connect-history-api-fallback
- HotModuleReplacementPlugin
- friendly-errors-webpack-plugin
- 发布环境
- tree shaking
- source map
- Plugin
- ExtractTextPlugin
- DefinePlugin
- CommonsChunkPlugin
- inline-manifest-webpack-plugin
- HashedModuleIdsPlugin
- optimize-css-assets-webpack-plugin
- ModuleConcatenationPlugin
css loaders
基本知识
entry
entry: { [entryChunkName:string]: string | Array(string) }
,配置入口文件的地址,可以是string或者string组成的array
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
output
output: {
filename: string // 打包输出的文件名
path: string // 打包输出文件的路径
chunkFilename: string // 代码分离时打包输出的块的名字
publicPath: string // 配置资源的CDN或者hash地址
}
externals
防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
<script src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous"></script>
externals : {
jquery: 'jQuery'
}
import $ from 'jquery'
就会在运行时去获取通过 <script src="...">
引入的jquery
externals : {
lodash : {
commonjs: "lodash",
amd: "lodash",
root: "_" // 指定全局变量
}
}
// or
externals : {
subtract : {
root: ["math", "subtract"] //转换为父子结构,其中 math 是父模块,而 bundle 只引用 subtract 变量下的子集。
}
}
通过window['math']['subtract']
访问subtract
loaders
loaders是模块源码的转换器。可以将一些其他类型的语言(如typescript)等转换为javascript,内联图片和一些其他的资源,loaders甚至允许你在js中import css资源
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
在module.rules定义一些列loaders
- loaders可以被串联在一起。如
sass: 'vue-style-loader!css-loader?sourceMap!sass-loader?indentedSyntax&sourceMap'
- loaders可以是同步或者异步的
- loaders运行在Nodejs中,所以可以使用Nodejs的资源
- loaders可以接受query参数
- loaders也可以配置options参数
- 插件可以给loaders提供更多特性
使用方法
loader的options:: 修改文件名,放在输出目录下,并返其对应的 url
test: 检测哪些文件需要此loader,是一个正则表达式
exclude: 忽略哪些文件
file-loader
处理资源文件,寻找资源文件并将其放在输出目录里面,为了更好地缓存文件,可以使用版本hash为资源文件命名。
require("file?name=html-[hash:6].html!./page.html");
// => html-109fa8.html
资源路径规则:
- 相对URLs:如
./assets/logo.png
会解释成require('./assets/logo.png')
- 没有前缀URLs:如
assets/logo.png
会被当做相对路径./assets/logo.png
-
~
为前缀:如~assets/logo.png
解释成require('assets/logo.png')
- 绝对路径: 不会被处理
url-loader
如果图片大小小于一个limit,转化为base64,否则回退到file-loader的功能
html-loader
把Html文件输出成字符串。它默认处理html中的[图片上传失败...(image-58f768-1513065689939)]
为require("./image.png")
,同时需要在你的配置中指定image文件的加载器,比如:url-loader或者file-loader。
css-loader、postcss-loader、style-loader
postcss-loader:对css应用autoprefixer
css-loader:解决css文件中的路径问题,将css中资源作为依赖:如background: url(./logo.png)
,webpack以获取模块的方式来获取图片
style-loader:将css转化为js模块,注入 <style>标签
loader: 'style!css!postcss'
sass-loader
将sass解析成css,需要node-sass、webpack作为依赖。query parameters:https://github.com/sass/node-sass
Plugins
plugins是webpack的“骨”,webpack自己也是建立在plugins之上的。plugins可以做一些loaders做不了的工作
plugins: [
new webpack.optimize.UglifyJsPlugin(), // 丑化代码
new HtmlWebpackPlugin({template: './src/index.html'}) // 提供html模板
]
reslove
resolve.modules
在resolve模块的时候,告诉webpack去哪些文件夹搜索
modules: [
resolve('src'),
resolve('node_modules')
]
如果src文件夹下面有个a文件,那么就可以直接 require('a'),webpack就会去src文件夹下面寻找a。
会顺着引用依赖的文件目录逐级向上寻找,直至找到目标文件。例如 文件夹目录为
那么如果在
main.js
中import 'a'
会逐级向上搜索,在第一个src
下面找到a
文件就会返回这个文件对象,停止搜索
resolve.alias
alias: {
vue$: 'vue/dist/vue.common.js',
Utilities: path.resolve(__dirname, 'src/utilities/')
}
那么import 'src/utilities/a.js'
就可以简写成import 'Utilities/a.js'
加一个$
用于精确匹配路径。
resolve.extensions
用于搜索指定的扩展名
extensions: [".js", ".json", ".vue", ".ts"]
resolve.mainFields
当引入一个package.json中的包时,mianField字段决定了check这个包的package.json的哪个字段
当target设置为web、webworker或者未指定时,mainFields: ["browser", "module", "main"]
否则mainFields: ["module", "main"]
例如import * as D3 from "d3"
, D3包的package.json
{
...
main: 'build/d3.Node.js',
browser: 'build/d3.js',
module: 'index',
...
}
就会从browser
字段对应的地址(此处是build/d3.js
)去寻找文件(按照mainFileds定义的先后顺序寻找文件)
resolve.mainFiles
当查找到的是一个文件目录,那么mainFiles决定了这个目录下查找的文件名
mainFiles: ["main"]
那么解决到文件目录时,返回main名字的文件
configuration、modules、modules resolution
configuration
webpack的配置文件就是导出对象的js文件。因为他是标准的Nodejs Commonjs模块,所以可以
- 通过require(...)引入文件
- 使用js的控制流表达式,如
?:
- 使用constants(常量)和variables(变量)
- 编写和使用函数
webpack接受多种语言编写的配置文件。支持的文件后缀可以在node-interpret中找到。webpack能够处理Typescript、coffeeScript、Babel和JSX等编写的配置文件
除了导出一个对象文件,webpack还可以导出一个函数、一个promise和一个数组(数组里面是多种配置)
modules
在编程时,开发人员将程序分割成多个离散的成为功能的模块。编写良好的模块具有封装性和抽象性。应用程序中的每个模块都有一致的设计和清晰的目的,如验证、调试和测试
webpack的模块能够可以通过以下方式声明他们的依赖
-
ES2015的
import
声明 -
CommonJS的
require
声明 -
AMD 的
define
和require
声明 - 在css/sass/less 文件@import 声明
- 样式表 (url(...)) 或者html (<img src=...>) 文件中的图片url
通过loaders,webpack支持一系列的语言和预处理器。loaders告诉webpack如何处理非js模块以及将这些模块打包到bundles里面。
modules Resolution
一个文件可以作为另外一个文件的依赖,reslover就是定位依赖文件(找到文件的绝对路径)的解决方案。
绝对路径
import "/home/me/file";
import "C:\\Users\\me\\file";
相对路径
找到require
和import
发生的上下文目录。上下文目录的路径和相对路径拼接形成文件的绝对路径
modlue 路径
将搜索定义在 resolve.modules中的所有目录。也可以通过resolve.alias设置文件目录的别名
- 如果最后resolve的路径是一个文件:
如果路径带有文件名后缀,那么久定位到了文件。否则,定位以resolve.extensions选项中的后缀名为后缀的文件 - 如果最后resolve路径是一个目录
- 如果目录下包含package.json,按照resolve.mainFields的options的先后顺序查找文件
- 如果没有package.json字段或者mainFields没有返回有效的路径,就会按照先后顺序在resolve.mainFiles中查找
- extension也是在
resolve.extensions
选项中找
resolve loaders
方法和查找文件的方法一致。只是使用resolveLoader
Targets
由于js可以用在浏览器和服务器端。webpack提供了target,例如
module.exports = {
target: 'node'
};
webpack会将源代码编译成适用于Node.js类似环境运行的目标代码,
由于不能target不能是一个array,所以可以通过以下形式配置多个target环境
var path = require('path');
var serverConfig = {
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.node.js'
}
//…
};
var clientConfig = {
target: 'web', // <=== can be omitted as default is 'web'
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.js'
}
//…
};
module.exports = [ serverConfig, clientConfig ];
manifest
webpack打包的应用主要包括三部分代码
- 你或者你的团队成员编写的源代码
- 第三方库或者你的代码依赖的“vendor”
- 指挥模块间交互的runtime或者manifest
runtime
runtime的代码是在浏览器中运行的,主要包括loading和resolving逻辑。用于连接模块,控制模块间的交互
manifest
应用程序编译的时候,会保存一份关于所有模块的详细记录,这就是manifest,通过manifest,runtime会知道去哪里提取模块
在实际应用中,会使用CommonsChunkPlugin
打包公共模块
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module) {
if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
return false;
}
return module.context && module.context.indexOf("node_modules") !== -1;
}
})
打包出来的vendor文件会包含应用程序文件的chunkId和chunkHash等信息
如
entry: {
main: './commonJs/c',
main1: './commonJs/c1',
main2: './commonJs/c2',
main3: './commonJs/c3',
main4: './commonJs/c4',
}, // 一般entry文件只有一个,这里仅是一个说明例子
output: {
path: \_\_dirname + '/dist',
filename: "[name].[chunkhash:7].js"
}
打包出来的vendor会包含以下一段代码
var head = document.getElementsByTagName('head')[0];
script.src = __webpack_require.p + "" + chunkId + "." + ({"0":"main","1":"main1","2":"main2","3":"main3","4":"main4","5":"commons"}[chunkId]||chunkId) + "." + {"0":"0c9ed2","1":"9bef64","2":"dfadbc","3":"21a5a9","4":"d8fc6e","5":"7a6ed2"}[chunkId] + ".js";
head.appendChild(script);`
那么入口任意一个文件有些许变化,都会导致chunkId或者chunkHash的变化,从而导致整个vendor文件的重新加载,解决方法就是将这段代码提取出来,这段提取出来的代码即manifest
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
code split、懒加载
有时候打包出来的模块太大,可能会影响模块的加载速度,而有些代码不必马上加载,此时可以通过import实现代码分离
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'lodash'
);
lodash
将会另外打包成一个js文件,而不会打包至当前的js文件中。
webpackChunkName: 打包输出的文件名,
webpackChunkName
对应webpack配置中output: { chunkFilename: '[name].[chunkhash:6].js' }
里面的namewebpackMode:
-
"lazy"
(default): 对每一个import()
的模块创建一个可以懒加载的chunk -
"lazy-once"
: 对所有的import()
模块创建一个可以懒加载的chunk -
"eager"
: 不创建额外的chunk,所以也不会有额外的网络请求。对比静态import
,import
返回一个resolved的promise,在返回resolved的promise,模块不会执行 "weak"
以下方法可以将a,b模块打包到一个chunk中
() => import(/* webpackChunkName: "a" */ './a')
() => import(/* webpackChunkName: "a" */ './b')
注意:
- 如果
.babelrc
的配置中"comments": false
, 注释会失效,webpackChunkName
,webpackMode
也就失效了 -
.babelrc
配置需要引入"syntax-dynamic-import"
,即plugins: ["syntax-dynamic-import"]
Caching
通过webpack最后输出了一个可以部署在服务器中的dist
目录。浏览器从服务器获取网站和资源,资源的获取可能会耗时,所以浏览器提供了caching技术,即资源没有改变时,浏览器就会使用缓存,不会重新去服务器获取新的代码
webpack通过根据文件内容生成hash,内容变化导致hash变化,进而重新去服务器请求新的code。
output: {
filename: "[name].[chunkhash:6].js", // 配置入口打包输出的hash
chunkFilename: 'js/[name].[chunkhash:6].js' // 配置chunk的hash
},
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/img/[name].[hash:7].[ext]' // 配置图片资源的hash
}
}
]
}
]
}
plugins: [
new extractTextPlugin({
filename: "[name].[contenthash:6].css", // 提取出来的css文件的hash
allChunks: true // 有分离文件的样式也会全部压缩到一个css文件上
})
]
开发和发布环境配置
发布环境的配置和开发环境不一样。在开发环境我们需要配置一个支持刷新或者热更新的localhost的服务器,需要source map。在发布环境,我们关注于如何缩小输出的bundle的体积,使用轻便的source map,优化资源来提升加载速度。通常建议对不同的环境写一个不同的配置。
对于两个环境,会有一些相同的配置,比如,入口文件,输出文件地址等。我们一般提取出相同的配置至webpack.common.js
中,
通用配置
了解了基础配置以后,可以开始配置部分开发环境和发布环境的配置。
首先,我们有一个webpack.common.js
,这个文件包含开发环境和测试环境的公共配置
const path = require('path');
//一个公共方法,用于resolve文件
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: [resolve('./src/main.js')]
},
output: {
filename: '[name].js',
path: resolve('dist'),
publicPath: '/'
},
resolve: {
extensions: ['.js'],
modules: [
resolve('src'),
resolve('node_modules')
],
alias: {
'src': resolve('src')
}
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: resolve('src')
},
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/img/[name].[hash:7].[ext]'
}
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/img/[name].[hash:7].[ext]'
}
}
]
}
]
}
}
babel-loader
: 对es6代码做一些处理,转化成适合在浏览器中运行的js代码。
url-loader
: 如果图片大小小于一个limit,转化为base64,否则回退到file-loader的功能。所以再使用url-loader
时候,要按照file-loader
依赖
公共插件
webpack-merge
现在有一个公共的webpack配置webpack.common.js
,在特定的development或者production环境的webpack配置中,我们需要合并公共配置,就可以使用webpack-merge
var commonConfig = require('./webpack.common.js')
const merge = require('webpack-merge')
const config = merge(commonConfig, {
module: {
rules: [
...
]
},
devtool: '#source-map',
plugins: [
...
]
})
HtmlWebpackPlugin
此插件用于生产一个html文件。可以用HtmlWebpackPlugin
直接生成html文件,也可以根据你提供的html文件,根据HtmlWebpackPlugin
生成一个html文件。将这个生成的html文件打包到输出中。
基本用法:
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');
var webpackConfig = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin()]
};
会生成一个包含以下内容的dist/index.html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>webpack App</title>
</head>
<body>
<script src="index_bundle.js"></script>
</body>
HtmlWebpackPlugin
的配置项
template
:html5模板地址,根据该template生成html
inject
:true | 'head' | 'body' | false,inject
为true或 'body',所有的js文件会注入body元素的最后面。inject
为head时,js会注入head中
filename
:生成的html文件名
title
: html文件title
chunks
:只添加指定的chunks
excludeChunks
:指定不添加某些chunks
chunksSortMode
:控制chunk的排序,有'none' | 'auto' | 'dependency' | {function} ,默认auto
DefinePlugin
创建全局的变量
new webpack.DefinePlugin({
DEV: JSON.stringify(true)
PRODUCTION: JSON.stringify(false)
})
如在只在测试环境下打印出一些log的时候,就会很有用,如:
DEV && (console.log('这是一个只会在测试环境下打印出的信息'))
开发环境
打造一个应用的开发环境。在开发环境我们需要source map,需要配置一个支持刷新或者热更新的localhost的服务器,
使用source map
由于浏览器中运行的代码都是打包输出的bundle,当你的代码出现错误或者警告的时候,可以通过source map定位到出错的源代码。此处以inline-source-map
为例(更多devtool选择)
devtool: 'inline-source-map'
配置一个实现热更新的localhost的服务器
- webpack-dev-server:一个简单的web服务器。使用webpack-dev-server后,任意修改一个文件,保存后,编译完成后刷新浏览器,就能看到更新。webpack-dev-server会把编译后的文件存放到内存里面,而不是输出到文件目录。
- webpack-dev-middleware:webpack-dev-middleware就是一个中间件,将webpack的修改发射给服务器。webpack-dev-server的内部也使用了webpack-dev-middleware
- webpack-hot-middleware:结合webpack-hot-middleware使用,实现无刷新更新(hot reload)
express可以用于构建一个web服务器,webpack-dev-middleware和webpack-hot-middleware都是适用于express的中间件,可以通过express构建一个可以启动一个适用于热更新的应用
development.js
const app = require('express')()
const webpack = require('webpack')
const config = require('./webpack.dev.js') // 获取测试环境的webpack配置
const compiler = webpack(config)
const port = 3000
//告诉webpack-dev-middleware去使用webpack配置
const devMiddle = require('webpack-dev-middleware')(compiler, {
quiet: true,
publicPath: config.output.publicPath
})
//告诉webpack-hot-middleware去使用webpack配置
const hotMiddle = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
//告诉express服务器去使用中间件
app.use(devMiddle)
app.use(hotMiddle)
app.listen(port, function (err) {
if (err) {
console.log(err)
} else {
console.log('Listening at http://localhost:' + port + '\n')
}
})
在package.json
中,通过"scripts": { "start": "node development.js", },
就可以启动一个localhost服务
更多参考Express结合Webpack的全栈自动刷新
然后在每个entry后面增加一个hotMiddlewareScript,告诉浏览器在不能hot reload的时候,整页刷新
var hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true'
module.exports = {
entry: {
app: ['./src/main.js', hotMiddlewareScript]
}
}
完成上述配置后,包含HMR(热更新)的模块在代码被修改后就可以实现热更新。通常会全局开启代码热替换,可以通过插件webpack. HotModuleReplacementPlugin()
实现
plugins: [
new webpack. HotModuleReplacementPlugin()
]
适用于开发环境的一些其他插件
connect-history-api-fallback
在使用h5 history的api的时候,获取不到index.html
的页面就会返回404
错误,这时候就需要使用connect-history-api-fallback
,对其他页面的请求也发送index.html
给浏览器端
app.use(require('connect-history-api-fallback')())
app.use(devMiddleware)
app.use(hotMiddleware)
注意:connect-history-api-fallback
中间件需要在devMiddleware
之前使用
friendly-errors-webpack-plugin
友好的输出错误提示
plugins: [ new FriendlyErrorsPlugin({}) ]
开发环境webpack.dev.js
的最终配置
const commonConfig = require('./webpack.common.js')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const webpack = require('webpack')
var hotMiddlewareScript = ['webpack-hot-middleware/client?noInfo=true&reload=true']
commonConfig.entry.app = commonConfig.entry.app.concat(hotMiddlewareScript)
var outputConfig = merge(commonConfig, {
module: {
rules: [
// 对css处理的loader
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
}
{
loader: 'css-loader'
option: {
sourceMap: true
}
}
]
}
]
},
devtool: '#source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
__DEV__: true,
__PRODUCTION__: false
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.dev.html',
inject: true
}),
new FriendlyErrorsPlugin({})
]
})
module.exports = outputConfig
发布环境
在发布环境,我们关注于如何缩小输出的bundle的体积,使用轻便的source map,优化资源来提升加载速度
tree shaking(UglifyJSPlugin)
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
上面的代码,如果你只用到了cube
函数,打包后会发现square
函数也在输出的bundle里面
把你的代码想象成一棵树,绿色的叶子代表有用的代码(如上述的cube),棕色的死了的叶子代表没有用到的代码(如上述的square),那么你用劲摇晃这棵树,希望将死掉的叶子摇晃下来(在output的bundle中剔除无用的代码)。这就是tree shaking
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [new UglifyJSPlugin(
compress: {
warnings: false
}
)]
}
uglifyjs-webpack-plugin
还会对代码进行压缩丑化,会大大减小打包输出文件的体积。
要实现tree shaking的效果需要注意:
- 要实现es6模块语法(如:
import
和export
) - 能够丑化代码并且支持无用代码移除的插件(如UglifyJSPlugin)
source map
在运行基准测试的时候,source map是非常有用的,因此,建议在发布环境也配置source map,建议配置
module.exports = merge(common, {
devtool: 'source-map', // 使用'source-map'选项
plugins: [
new UglifyJSPlugin({
sourceMap: true
})
]
})
ExtractTextPlugin
在打包输出的文件中,css也会被打包至js文件中。我们需要把这一部分css文件从js文件中提取至一个单独的css文件中。就需要用到ExtractTextPlugin
,使用方法
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
})
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'style-loader'
})
}
]
}
plugins: [
new ExtractTextPlugin({
filename: '[name].[contenthash:6].css',
allChunks: true
})
]
new ExtractTextPlugin(option)
这个option是一个对象,包含一个filename属性,表示打包输出的css文件名。
'[name].[contenthash:6].css'
,其中name
表示打包输出的文件名,contenthash
表示根据文件内容生成的哈希,contenthash:6
表示长度为6
ExtractTextPlugin.extract(options: loader | object)
options.use
:string | Array | object。表示把资源文件转化为css文件需要用到的loaders
options.fallback
: css没有提取到的时候使用的loader
关于更多loaders的处理
module: {
rules: [
{
test: /\.css$/,
use: extractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: false
}
}
],
fallback: 'style-loader'
})
},
{
test: /\.(sass|scss)$/,
use: extractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: false
}
},
{
loader: 'sass-loader',
options: {
sourceMap: false
}
}
],
fallback: 'style-loader'
})
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
loaders: {
css: extractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
}),
sass: extractTextPlugin.extract({
use: 'css-loader!sass-loader?indentedSyntax',
fallback: 'vue-style-loader'
}),
stylus: extractTextPlugin.extract({
use: 'css-loader!stylus-loader',
fallback: 'vue-style-loader'
}),
styl: extractTextPlugin.extract({
use: 'css-loader!stylus-loader',
fallback: 'vue-style-loader'
}),
}
}
}
}
]
}
extract-text-webpack-plugin的更多用法
DefinePlugin
定义一些常量,用于判断环境后执行一些特性环境下执行的logging或者testing,易于测试
plugins: [
new webpack.DefinePlugin({
DEV: JSON.stringify(false),
PRODUCTION: JSON.stringify(true)
})
]
或者
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
CommonsChunkPlugin
提取多个入口的公共代码
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module) {
// This prevents stylesheet resources with the .css or .scss extension
// from being moved from their original chunk to the vendor chunk
if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
return false;
}
return module.context && module.context.indexOf("node_modules") !== -1;
}
}),
new webpack.optimize.CommonsChunkPlugin({
// 将vendor中的运行时(Runtime)提取至manifest中
name: 'manifest'
})
提取出来的manifest文件非常的小(只有1000多字节),可以将manifest文件内联至index.html
中,节省http请求,这时候就可以用到inline-manifest-webpack-plugin
插件
inline-manifest-webpack-plugin
const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')
plugins: [ new InlineManifestWebpackPlugin() ]
然后在html的body中添加<%=htmlWebpackPlugin.files.webpackManifest%>
即可
<body>
<%=htmlWebpackPlugin.files.webpackManifest%>
</body>
执行后,打开dist中的index.html
文件,可以看到没有引入manifest
文件,而是有一段内联的manifest
代码
__ HashedModuleIdsPlugin__
比如,我们使用code split引入a, b, c, d四个文件,打开dist中的vendor文件可以看到
有一段
webpackJsonp([5]
表示vendor自身的chunkId
之后进行修改,只引入a,c,d三个文件。再打开dist/vendor.js
,可以看到webpackJsonp([5]
变成了webpackJsonp([4]
可以看出a,c,d三个文件以及vendor文件可能会因为自身chunkId的改变而变化,导致浏览器重新加载,使用HashedModuleIdsPlugin可以避免该问题
plugins: [ new webpack.HashedModuleIdsPlugin() ]
optimize-css-assets-webpack-plugin
对提取出来的css文件做优化,可以比较使用optimize-css-assets-webpack-plugin
前后的css文件体积,会缩小很多
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin")
plugins: [ new OptimizeCssAssetsPlugin() ]
__ ModuleConcatenationPlugin__
将一些有联系的模块,放到一个闭包函数里面去,通过减少闭包函数数量从而加快JS的执行速度。
更多解释
最后webpack.production.js
配置
var commonConfig = require('./webpack.common.js')
const merge = require('webpack-merge')
const extractTextPlugin = require('extract-text-webpack-plugin')
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin")
module.exports = merge(commonConfig, {
output: {
filename: "[name].[chunkhash:6].js",
chunkFilename: 'js/[name].[chunkhash:6].js',
publicPath: '/' // 此处根据实际情况填写资源存放的publicPath地址
},
plugins: [
new extractTextPlugin({
filename: "[name].[contenthash:6].css",
allChunks: true
}),
new webpack.DefinePlugin({
DEV: false,
PRODUCTION: true,
'process.env.NODE_ENV': '"production"'
}),
new uglifyjsWebpackPlugin({
compress: {
warnings: false
},
sourceMap: false
}),
new webpack.HashedModuleIdsPlugin(),
new OptimizeCssAssetsPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
// 保证vendor在app前加载
chunksSortMode: 'dependency'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module) {
if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
return false;
}
return module.context && module.context.indexOf("node_modules") !== -1;
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
new InlineManifestWebpackPlugin()
],
devtool: '#source-map'
})
css loaders
建议使用vue-cli封装的utils.js
,已经对当前流行的各种类型的css进行了封装处理
例如utils.js
'use strict'
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
vue-loader.conf.js
'use strict'
const utils = require('./utils')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
const cacheBustingEnabled = !isProduction
const loaders = {
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: cacheBustingEnabled,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
module.exports = loaders
在webpack.common.js
中,只需要
// 对.vue文件中引入的样式做处理
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: require('./vue-loader.conf')
}
}
在webpack.dev.js
中,添加代码
merge(commonConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: false,
extract: false,
usePostCSS: true
})
}
})
在webpack.production.js
中,添加代码
merge(commonConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: true,
extract: true,
usePostCSS: true
})
}
})
其他的plugins
CopyWebpackPlugin
用于拷贝文件
portscanner
var portscanner = require('portscanner')
// Checks the status of a single port
portscanner.checkPortStatus(3000, '127.0.0.1', function(error, status) {
// Status is 'open' if currently in use or 'closed' if available
console.log(status)
})
// Find the first available port. Asynchronously checks, so first port
// determined as available is returned.
portscanner.findAPortNotInUse(3000, 3010, '127.0.0.1', function(error, port) {
console.log('AVAILABLE PORT AT: ' + port)
})
// Find the first port in use or blocked. Asynchronously checks, so first port
// to respond is returned.
portscanner.findAPortInUse(3000, 3010, '127.0.0.1', function(error, port) {
console.log('PORT IN USE AT: ' + port)
})
check-dependencies
require('check-dependencies')(config, callback);
或者
require('check-dependencies')(config)
.then(function (output) {
/* handle output */
});
callback = {
status: number, // 0 if successful, 1 otherwise
depsWereOk: boolean, // true if dependencies were already satisfied
log: array, // array of logged messages
error: array, // array of logged errors
}
config = {
packageManager: string // 'npm'(default) 或者'bower'
packageDir: package.json或者bower.json目录
install:安装缺失的package,默认false
scopeList:去哪些keys寻找package的名字和版本,默认['dependencies', 'devDependencies']
verbose:打印message
log:打印dubug信息的function,verbose必须为true
error:打印error信息的function,verbose必须为true
}
webpack中的资源优化
1 url-loader:将小的图片转为base64,内联在html中,减少资源的http请求
2 将一些不常改变的公共模块通过commonChunksPlugin抽离成一个或几个单独的js文件。缓存在本地,减少http请求
3 通过UglifyPlugin丑化文件,缩小文件体积
4 通过optimizeCssAssetsPlugin处理css文件,可以大幅度减少css文件体积
5 通过code spliting(es6的import()方法可以实现)实现按需加载,加速首屏加载
6 通过inlineManifestPlugin将体积较小的manifest文件内联在html中,避免多余的http请求
7 根据文件内容生成hash,用hash作为文件名。内容变化则hash变化,本地缓存失效,重新请求http。内容不变化情况下,使用本地缓存,减少http请求(有output的filename、chunkFilename的chunkHash、ExtractTextPlugin的contenthash,图片等资源的hash)
8 通过hashedModulesPlugin处理输出的文件,避免因文件名hash变化引起的缓存失效,重新请求资源