title: webpack
date: 2017-09-18 11:05:40
tags:
什么是 webpack?
一种打包工具!
在 webpack 的机制里, 所有的资源都是模块, 报错 js, css, 图片等, 且可以通过代码分割的方法异步加载.
webpack 将模块及其依赖打包生成静态资源.
它做了什么?
分析你的项目结构, 找到 JavaScript 模块以及其他的浏览器不能直接运行的拓展语言(Scss, TypeScript 等), 将其转换和打包为适合的格式供浏览器使用.
特点
代码拆分:
Webpack 有 同步 和 异步 两种模块依赖的方式. 异步依赖作为一个分割点, 形成一个新的块. 优化依赖树后, 每一个异步区块作为一个文件被打包.
Loader:
loader 转换器帮助 webpack 处理各种类型的资源, 将他们转换成 JavaScript 模块.
智能解析:
Webpack 有一个智能解析器, 几乎可以处理任何第三方库.
插件系统:
Webpack 大多数内容功能都是基于这个插件系统运行的, 还可以开发和使用开源的 Webpack 插件.
快速运行:
Webpack 使用异步 I/O 和多级缓存提高运行效率.
一个配置文件的栗子🌰
const {
DefinePlugin,
/* 修改模块 id 为 hash 值, 而非自增的数字 id */
HashedModuleIdsPlugin,
optimize: { UglifyJsPlugin }
} = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
// const WebpackChunkHash = require("webpack-chunk-hash");
const HashedChunkIdsPlugin = require('@58qf/hashed-chunkid-webpack-plugin');
// const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
//const VisualizerPlugin = require('webpack-visualizer-plugin');
/* 抽取本应包含在 Entry Chunk 中的 manifest 对象为单独的 json 文件,
只在使用到了异步代码块时候使用到 manifest 对象 */
const AssetsPlugin = require('assets-webpack-plugin');
// const AssetsPlugin = require('@58qf/assets-chunkmanifest-webpack-plugin');
const Merge = require('webpack-merge');
const CommonConfig = require('./webpack.common.js');
const { readdirSync } = require('fs');
const { resolve } = require('path');
let { entryDir, publicPath, host } = require('../config');
const prodConfig = function (env = 'test') {
const isProd = env === 'production';
let entry = readdirSync(entryDir).filter(element => {
return element.indexOf(".") === -1;
})
.reduce((entries, el) => {
entries[el] = `./entries/${el}`;
return entries;
}, {});
let plugins = [
new ExtractTextPlugin({
filename: `css/[name]${isProd ? '_[contenthash:8]' : ''}.css`,
allChunks: true
}),
new DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify(env)
}
}),
// new AssetsPlugin({
// path: resolve('dist'),
// metadata: { publicPath, version: Date.now() }
// }),
new HashedModuleIdsPlugin(),
new HashedChunkIdsPlugin(),
// new WebpackChunkHash(),
// new ChunkManifestPlugin({
// filename: 'manifest.json',
// manifestVariable: 'webpackManifest',
// }),
new UglifyJsPlugin({
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
},
compress: {
screw_ie8: true,
warnings: false,
drop_console: isProd,
},
sourceMap: true,
comments: false,
}),
new CopyWebpackPlugin([
{
from: '../static/images',
to: `images/[name].[ext]`
},
{
from: '../static/scripts',
to: `scripts/[name].[ext]`
},
{
from: '../static/views',
to: `views/[name].[ext]`
},
{
from: '../static/res',
to: `res/`
},
]),
];
if (isProd) {
publicPath = host + publicPath;
plugins.push(new AssetsPlugin({
path: resolve('build'),
filename: 'assets-manifest.json',
includeManifest: 'runtime',
update: true,
prettyPrint: true,
metadata: { publicPath, version: Date.now() }
}));
}
return Merge(CommonConfig, {
devtool: 'source-map',
entry,
output: {
path: resolve('dist'),
publicPath,
pathinfo: !isProd,
chunkFilename: `scripts/[name]${isProd ? '_[chunkhash:8]' : ''}.js`,
filename: `scripts/[name]${isProd ? '_[chunkhash:8]' : ''}.js`,
sourceMapFilename: 'sourcemaps/[name].js.map'
},
module: {
rules: [
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
extractCSS: true
}
}
},
{
test: /\.(css|scss)$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
})
},
{
test: /\.(jpg|png|gif|woff|woff2|eot|ttf|svg)$/,
use: {
loader: 'url-loader',
options: {
limit: 1024,
name: `images/[name]${isProd ? '_[hash:8]' : ''}.[ext]`
}
}
}
]
},
plugins,
});
};
module.exports = prodConfig;
入口起点
单文件入口
const config = {
entry: './src/main.js'
};
module.exports = config
多文件入口
entry 的值可以是字符串, 数组, 以及对象形式
对象形式的多文件入口
const config = {
entry: {
app1: './src/app1.js',
app2: './src/app2.js'
}
};
module.exports = config;
输出
入口可以存在多个入口起点, 但只指定一个输出配置.
单文件输出:
const path = require('path');
const config = {
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
}
};
module.exports = config;
filename 和 path 是 output 最基本的配置.
其他配置还有:
chunkFilename, 给 chunkfile 的命名, chunkfile 指不是入口文件, 但又由于一些原因(大多是异步的原因)而单独生成了 chunk.
publicPath, 用于控制打包文件的相对或者绝对引用路径.
多文件输出: 使用占位符
const path = require('path');
const config = {
entry: {
app1: './src/app1.js',
app2: './src/app2.js'
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, 'dist')
}
};
module.exports = config;
webpack 中常见的占位符有多种,常见的如下:
[name] :代表打包后文件的名称,在entry 或代码中(之后会看到)确定;
[id] :webpack 给块分配的内部chunk id ,如果你没有隐藏,你能在打包后的命令行中看到;
[hash] :每次构建过程中,生成的唯一 hash 值;
[chunkhash] : 依据于打包生成文件内容的 hash 值,内容不变,值不变;
[ext] : 资源扩展名,如js ,jsx ,png 等等;
代码块 (Chunk)
文件级别的代码块, Chunk 大致分为三类
Entry Chunk
入口代码块包含了 webpack 运行时需要的一些函数,如 webpackJsonp, webpack_require 等以及依赖的一系列模块。
Normal Chunk
普通代码块没有包含运行时需要的代码,只包含模块代码,其结构有加载方式决定,如基于 CommonJs 异步的方式可能会包含 webpackJsonp 的调用。
Initial Chunk
与入口代码块对应的一个概念是入口模块(module 0),如果入口代码块中包含了入口模块 webpack 会立即执行这个模块,否则会等待包含入口模块的代码块,包含入口模块的代码块其实就是 initial chunk。
插件中最为特别的一个插件应该是 CommonsChunkPlugin, 不使用该插件的前提下暂且可以认为 Entry Chunk === Initial chunk, 多 Entry 的配置也是产出多个 Entry Chunk.
使用该插件后状况较为复杂, 多入口文件中的公共模块代码 以及 webpack runtime 的 bootstrap 代码会被抽取到插件生成的最后一个 Common chunk 中, 即该 Common Chunk === Entry Chunk , 其余模块集中产出一个 bundle.js , 没错, 这哥们就是 Initial Chunk.
详见 webpack 产物一窥
模块 (Module)
全局上, 我们将 Css, 图片以及字体文件等等也都分别看做一个模块. 也是 webpack 的黑魔法之一.
ES2015 import 语句
CommonJS require() 语句
AMD define 和 require 语句
css/sass/less 文件中的 @import 语句。
样式(url(...))或 HTML 文件(<img src=...>)中的图片链接(image url)
webpack 1 需要特定的 loader 来转换 ES 2015 import,然而通过 webpack 2 可以开箱即用。
模块解析
- 绝对路径
import "/home/me/file";
import "C:\\Users\\me\\file";
由于我们已经取得文件的绝对路径,因此不需要进一步再做解析。
- 相对路径
import "../src/file1";
import "./file2";
在这种情况下,使用 import 或 require 的资源文件(resource file)所在的目录被认为是上下文目录(context directory)。在 import/require 中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。
- 模块路径
import "module";
import "module/lib/file";
模块将在 resolve.modules 中指定的所有目录内搜索。 你可以替换初始模块路径,此替换路径通过使用 resolve.alias 配置选项来创建一个别名。
Loader
loader 在加载模块时预处理文件, loader 的配置是在 module.rules 中进行使, 每一项看做一个rule
- rule.test 用来正则匹配后缀名, 以确定处理改数据的 loader
- rule.use 指定对应的 loader 对文件进行相应的操作转换.
另外, rule 中其它一些规则也大多围绕匹配条件和应用结果展开, 例如 rule.exclude(不匹配), rule.include(匹配), rule.oneOf(只应用第一个匹配的 loader), rule.enforce(制定 loader 种类)
举个栗子🌰
npm css-loader style-loader sass-loader -D
const path = require('path');
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(css|scss)$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader'
}
]
}
]
}
};
module.exports = config;
插件
插件用来处理一些 loader 不能处理的问题, 但与 loader 不同的是, 使用插件需要先引入插件.
plugins 是一个数, 数组中的每一项都是某一个 plugin 的实例, plugins 数组甚至可以存在一个插件的多个实例
再举个栗子🌰
CommonsChunkPlugin 插件是用来提取 chunks 中的公共部分
const path = require('path');
const { optimize: { CommonsChunkPlugin } } = require('webpack');
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
}
]
}
},
plugins: [
new CommonsChunkPlugin({
name: 'runtime',
minChunks: Infinity,
}), ]
};
module.exports = config;
devtool
生成 sourcemap, 方便调试
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 选项具有相似的缺点 |
devServer
通过配置 dev-server 选项, 可以开启一个本地服务器,webpack 为本地服务器提供了非常多的配置选项
精细配置相关属性
-
resolve: 确定模块如何被解析, 除 webpack 的默认配置之外, 通过 resolve 的自定义配置, 对模块的解析实现刚更精细的控制.
又举个栗子🌰~
resolve: {
extensions: ['.js', '.json'],
modules: [join(__dirname, 'src'), 'node_modules'],
alias: {
'~': resolve('src'),
},
}
extensions 自动解析确定的扩展, 使用户在引入模块时可以不带扩展
modules 告诉 webpack 解析模块时应该搜索的目录
- externals: 打包生成的代码中不添加某项依赖, 这些依赖项直接从用户环境中取得.
🌰🌰🌰:
externals: {
jquery:'jQuery'
}
其他配置项详见 https://webpack.js.org/configuration/resolve/
webpack的安装
同大多数包一样, 需要 node.js 环境, 以及 npm
本地安装
npm install webpack -D
npm install webpack@tvelsieb> -D
webpack的使用
命令行带参
webpack.config 跑 webpack命令
package.json 内配 npm scripts配 npm
"scripts": {
"start": "webpack-dashboard -c magenta -- node dev_server/server.js",
"dev": "node dev_server/server.js",
"build:test": "rimraf dist && webpack --env=test --config build/webpack.prod.js",
"build:prod": " webpack --env=production --config build/webpack.prod.js --profile --json >> stats.json"
}
为多环境配置 webpack
现在在使用 webpack 的项目中, 大多会为多种环境配置不同的配置文件; 常用的方式是为开发环境和生产环境编写独立的配置, 公共部分可以抽出来;
也可以在同一个配置文件中编写两种环境的配置, 在 package.json 中添加命令时, 把环境变量传递给配置文件.