WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
构建就是把源代码转换成发布到线上的可执行 JavaScrip、CSS、HTML 代码,包括如下内容。
构建其实是工程化、自动化思想在前端开发中的体现,把一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。 构建给前端开发注入了更大的活力,解放了我们的生产力。
mkdir zhufeng-webpack
cd zhufeng-webpack
npm init -y
Webpack 启动后会从
Entry
里配置的Module
开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的Loader
去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个Chunk
。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。
npm install webpack webpack-cli -D
mkdir src
mkdir dist
webpack.config.js
const path=require('path');
module.exports={
context:process.cwd(),
entry: './src/index.js',
output: {
path: path.resolve(__dirname,'dist'),
filename:'bundle.js'
},
module: {},
plugins: [],
devServer: {}
}
const path=require('path');
module.exports={
entry: './src/index.js',
output: {
path: path.resolve(__dirname,'dist'),
filename:'bundle.js'
},
module: {},
plugins: [],
devServer: {}
}
在dist目录下创建index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
common
//parent chunk中解决了的chunk会被删除
optimization.removeAvailableModules:true
//删除空的chunks
optimization.removeEmptyChunks:true
//合并重复的chunk
optimization.mergeDuplicateChunks:true
development
//调试
devtool:eval
//缓存模块, 避免在未更改时重建它们。
cache:true
//缓存已解决的依赖项, 避免重新解析它们。
module.unsafeCache:true
//在 bundle 中引入「所包含模块信息」的相关注释
output.pathinfo:true
//在可能的情况下确定每个模块的导出,被用于其他优化或代码生成。
optimization.providedExports:true
//找到chunk中共享的模块,取出来生成单独的chunk
optimization.splitChunks:true
//为 webpack 运行时代码创建单独的chunk
optimization.runtimeChunk:true
//编译错误时不写入到输出
optimization.noEmitOnErrors:true
//给模块有意义的名称代替ids
optimization.namedModules:true
//给模chunk有意义的名称代替ids
optimization.namedChunks:true
production
//性能相关配置
performance:{hints:"error"....}
//某些chunk的子chunk已一种方式被确定和标记,这些子chunks在加载更大的块时不必加载
optimization.flagIncludedChunks:true
//给经常使用的ids更短的值
optimization.occurrenceOrder:true
//确定每个模块下被使用的导出
optimization.usedExports:true
//识别package.json or rules sideEffects 标志
optimization.sideEffects:true
//尝试查找模块图中可以安全连接到单个模块中的段。- -
optimization.concatenateModules:true
//使用uglify-js压缩代码
optimization.minimize:true
npm i webpack-dev-server –D
+ devServer:{
+ contentBase:path.resolve(__dirname,'dist'),
+ host:'localhost',
+ compress:true,
+ port:8080
+ }
+ "scripts": {
+ "build": "webpack",
+ "dev": "webpack-dev-server --open "
+ }
通过使用不同的Loader,Webpack可以要把不同的文件都转成JS文件,比如CSS、ES6/7、JSX等
加载CSS文件,CSS文件有可能在node_modules里,比如bootstrap和antd
module: {
rules: [
{
test: /\.css/,
+ loader:['style-loader','css-loader']
}
]
}
module: {
rules: [
{
test: /\.css/,
+ use:['style-loader','css-loader']
}
]
},
module: {
rules: [
{
test: /\.css/,
include: path.resolve(__dirname,'src'),
exclude: /node_modules/,
use: [{
loader: 'style-loader',
options: {
insert:'top'
}
},'css-loader']
}
]
}
cnpm i html-webpack-plugin -D
+ +entry:{
+ index:'./src/index.js', // chunk名字 index
+ common:'./src/common.js' //chunk名字 common
+ },
plugins: [
+ new HtmlWebpackPlugin({
+ template:'./src/index.html',//指定模板文件
+ filename:'index.html',//产出后的文件名
+ inject:false,
+ hash:true,//为了避免缓存,可以在产出的资源后面添加hash值
+ chunks:['common','index'],
+ chunksSortMode:'manual'//对引入代码块进行排序的模式
+ }),
)]
<head>
+ <% for (var css in htmlWebpackPlugin.files.css) { %>
+ <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
+ <% } %>
</head>
<body>
+ <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
+ <script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
+ <% } %>
</body>
npm i file-loader url-loader -D
let logo=require('./images/logo.png');
let img=new Image();
img.src=logo;
document.body.appendChild(img);
{
test:/\.(jpg|png|bmp|gif|svg)/,
use:[
{
loader:'url-loader',
options:{limit:4096}
}
]
}
还可以在CSS文件中引入图片
.logo{
width:355px;
height:133px;
background-image: url(./images/logo.png);
background-size: cover;
}
<div class="logo"></div>
因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来加载
import('module')
方法中引入的模块npm install --save-dev mini-css-extract-plugin
plugins: [
//参数类似于webpackOptions.output
+ new MiniCssExtractPlugin({
+ filename: '[name].css',
+ chunkFilename:'[id].css'
+ }),
{
test: /\.css/,
include: path.resolve(__dirname,'src'),
exclude: /node_modules/,
use: [{
+ loader: MiniCssExtractPlugin.loader
},'css-loader']
}
HtmlWebpackPlugin
的下面inject
设置为true
cnpm i html-inline-css-webpack-plugin -D
+const HtmlInlineCssWebpackPlugin= require('html-inline-css-webpack-plugin').default;
plugins:[
new HtmlWebpackPlugin({}),
+ new HtmlInlineCssWebpackPlugin()
]
terser-webpack-plugin
替换掉uglifyjs-webpack-plugin
解决uglifyjs不支持es6语法问题cnpm i uglifyjs-webpack-plugin terser-webpack-plugin optimize-css-assets-webpack-plugin -D
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
mode: 'production',
optimization: {
minimizer: [
/* new UglifyJsPlugin({
cache: true,//启动缓存
parallel: true,//启动并行压缩
//如果为true的话,可以获得sourcemap
sourceMap: true // set to true if you want JS source maps
}), */
new TerserPlugin({
parallel: true,
cache: true
}),
//压缩css资源的
new OptimizeCSSAssetsPlugin({
assetNameRegExp:/\.css$/g,
//cssnano是PostCSS的CSS优化和分解插件。cssnano采用格式很好的CSS,并通过许多优化,以确保最终的生产环境尽可能小。
cssProcessor:require('cssnano')
})
]
},
HtmlInlineCssWebpackPlugin
/images
,或者 {
loader:MiniCssExtractPlugin.loader,
options:{
+ publicPath:'/'
}
{
test:/\.(jpg|jpeg|png|bmp|gif|svg|ttf|woff|woff2|eot)/,
use:[
{
loader:'url-loader',
options:{
limit: 4096,
+ outputPath: 'images',
+ publicPath:'/images'
}
}
]
}
output: {
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js',
+ publicPath:'/'
},
{
test:/\.(jpg|jpeg|png|bmp|gif|svg|ttf|woff|woff2|eot)/,
use:[
{
loader:'url-loader',
options:{
limit: 4096,
+ outputPath: 'images',
+ publicPath:'/images'
}
}
]
}
plugins: [
new MiniCssExtractPlugin({
- //filename: '[name].css',
- //chunkFilename: '[id].css',
+ chunkFilename: 'css/[id].css',
+ filename: 'css/[name].[hash].[chunkhash].[contenthash].css',//name是代码码chunk的名字
}),
mini-css-extract-plugin
里的contenthash
值,保证即使css文件所处的模块里就算其他文件内容改变,只要css文件内容不变,那么不会重复构建指纹占位符
占位符名称 | 含义 |
---|---|
ext | 资源后缀名 |
name | 文件名称 |
path | 文件的相对路径 |
folder | 文件所在的文件夹 |
contenthash | 文件的内容hash,默认是md5生成 |
hash | 文件内容的hash,默认是md5生成 |
emoji | 一个随机的指代文件内容的emoj |
npm i less less-loader -D
npm i node-sass sass-loader -D
less
@color:red;
.less-container{
color:@color;
}
scss
$color:green;
.sass-container{
color:$color;
}
webpack.config.js
{
test: /\.less/,
include: path.resolve(__dirname,'src'),
exclude: /node_modules/,
use: [{
loader: MiniCssExtractPlugin.loader,
},'css-loader','less-loader']
},
{
test: /\.scss/,
include: path.resolve(__dirname,'src'),
exclude: /node_modules/,
use: [{
loader: MiniCssExtractPlugin.loader,
},'css-loader','sass-loader']
},
npm i postcss-loader autoprefixer -D
index.css
::placeholder {
color: red;
}
postcss.config.js
module.exports={
plugins:[require('autoprefixer')]
}
webpack.config.js
{
test:/\.css$/,
use:[MiniCssExtractPlugin.loader,'css-loader','postcss-loader'],
include:path.join(__dirname,'./src'),
exclude:/node_modules/
}
npm i babel-loader @babel/core @babel/preset-env @babel/preset-react -D
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
//Option+Shift+A
function readonly(target,key,discriptor) {
discriptor.writable=false;
}
class Person{
@readonly PI=3.14;
}
let p1=new Person();
p1.PI=3.15;
console.log(p1)
jsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true
}
}
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options:{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
},
include: path.join(__dirname,'src'),
exclude:/node_modules/
}
.babelrc
{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
_extend
@babel/runtime
作为一个独立模块,来避免重复引入Object.defineProperty
@babel/preset-env
中的 useBuiltIns 选项,如果你设置了 usage,babel 编绎的时候就不用整个 polyfills , 只加载你使用 polyfills,这样就可以减少包的大小@babel/plugin-transform-runtime
是开发时引入, @babel/runtime
是运行时引用npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
.babelrc
{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": true
}
]
]
}
webpack打包的时候,会自动优化重复引入公共方法的问题
npm install eslint eslint-loader babel-eslint --D
.eslintrc.js
module.exports = {
root: true,
//指定解析器选项
parserOptions: {
sourceType: 'module',
ecmaVersion: 2015
},
//指定脚本的运行环境
env: {
browser: true,
},
// 启用的规则及其各自的错误级别
rules: {
"indent": ["error", 4],//缩进风格
"quotes": ["error", "double"],//引号类型
"semi": ["error", "always"],//关闭语句强制分号结尾
"no-console": "error",//禁止使用console
"arrow-parens": 0 //箭头函数用小括号括起来
}
}
webpack.config.js
module: {
//配置加载规则
rules: [
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: "pre",
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { fix: true } // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
},
cnpm i eslint-config-airbnb eslint-loader eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks and eslint-plugin-jsx-a11y -D
.eslintrc.js
module.exports = {
"parser":"babel-eslint",
"extends":"airbnb",
"rules":{
"semi":"error",
"no-console":"off",
"linebreak-style":"off",
"eol-last":"off"
//"indent":["error",2]
},
"env":{
"browser":true,
"node":true
}
}
{
test:/\.(woff|ttf|eot|svg|otf)$/,
use:{
//url内部内置了file-loader
loader:'url-loader',
options:{//如果要加载的图片大小小于10K的话,就把这张图片转成base64编码内嵌到html网页中去
limit:10*1024
}
}
},
@font-face {
src: url('./fonts/HabanoST.otf') format('truetype');
font-family: 'HabanoST';
}
.welcome {
font-size:100px;
font-family: 'HabanoST';
}
source maps
文件,map
文件是一种对应编译文件和源文件的方法类型 | 含义 |
---|---|
source-map | 原始代码 最好的sourcemap质量有完整的结果,但是会很慢 |
eval-source-map | 原始代码 同样道理,但是最高的质量和最低的性能 |
cheap-module-eval-source-map | 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能 |
cheap-eval-source-map | 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl |
eval | 生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap |
cheap-source-map | 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用 |
cheap-module-source-map | 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射 |
看似配置项很多, 其实只是五个关键字eval、source-map、cheap、module和inline的任意组合
关键字 | 含义 |
---|---|
eval | 使用eval包裹模块代码 |
source-map | 产生.map文件 |
cheap | 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap |
module | 包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件 |
inline | 将.map作为DataURI嵌入,不单独生成.map文件 |
script.js
let a=1;
let b=2;
let c=3;
java -jar compiler.jar --js script.js --create_source_map ./script-min.js.map --source_map_format=V3 --js_output_file script-min.js
script-min.js
var a=1,b=2,c=3;
script-min.js.map
{
"version":3,
"file":"script-min.js",
"lineCount":1,
"mappings":"AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE;",
"sources":["script.js"],
"names":["a","b","c"]
}
字段 | 含义 |
---|---|
version:Source | Source map的版本,目前为3 |
file:转换后的文件名。 | 转换后的文件名 |
sourceRoot | 转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。 |
sources | 转换前的文件。该项是一个数组,表示可能存在多个文件合并。 |
names | 转换前的所有变量名和属性名 |
mappings | 记录位置信息的字符串 |
对应 | 含义 |
---|---|
第一层是行对应 | 以分号(;)表示,每个分号对应转换后源码的一行。所以,第一个分号前的内容,就对应源码的第一行,以此类推。 |
第二层是位置对应 | 以逗号(,)表示,每个逗号对应转换后源码的一个位置。所以,第一个逗号前的内容,就对应该行源码的第一个位置,以此类推。 |
第三层是位置转换 | 以VLQ编码表示,代表该位置对应的转换前的源码位置。 |
"mappings":"AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE;",
位置 | 含义 |
---|---|
第一位 | 表示这个位置在(转换后的代码的)的第几列 |
第二位 | 表示这个位置属于sources属性中的哪一个文件 |
第三位 | 表示这个位置属于转换前代码的第几行 |
第四位 | 表示这个位置属于转换前代码的第几 |
第五位 | 表示这个位置属于names属性中的哪一个变量 |
首先,所有的值都是以0作为基数的。其次,第五位不是必需的,如果该位置没有对应names属性中的变量,可以省略第五位,再次,每一位都采用VLQ编码表示;由于VLQ编码是变长的,所以每一位可以由多个字符构成
如果某个位置是AAAAA,由于A在VLQ编码中表示0,因此这个位置的五个位实际上都是0。它的意思是,该位置在转换后代码的第0列,对应sources属性中第0个文件,属于转换前代码的第0行第0列,对应names属性中的第0个变量。
以16来做示例吧
let base64 = [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
];
function encode(num) {
debugger;
let binary = (num).toString(2);// 10000 转成二进制
binary = num > 0 ? binary + '0' : binary + '1';//正数最后边补0,负数最右边补1 100000
//00001 00000
let zero = 5 - (binary.length % 5);//4
if (zero > 0) {
binary = binary.padStart(Math.ceil(binary.length / 5) * 5, '0');
}// 00001 00000
let parts = [];
for (let i = 0; i < binary.length; i += 5) {
parts.push(binary.slice(i, i + 5));
}
parts.reverse();// ['00000','00001']
for (let i = 0; i < parts.length; i++) {
if (i === parts.length - 1) {
parts[i] = '0' + parts[i];// ['100000','000001']
} else {
parts[i] = '1' + parts[i];
}
}
let chars = [];
for (let i = 0; i < parts.length; i++) {
chars.push(base64[parseInt(parts[i], 2)]);
}
return chars.join('')
}
//16需要二个字符
let ret = encode(16);
console.log(ret);
function getValue(char) {
let index = base64.findIndex(item => item == char);
let str = (index).toString(2);
str = str.padStart(6, '0');
str = str.slice(1, -1);
return parseInt(str, 2);
}
function decode(chars) {
let values = [];
for (let i = 0; i < chars.length; i++) {
values.push(getValue(chars[i]));
}
return values;
}
function desc(values) {
return `
第${values[1] + 1}个源文件中
的第1行
第${values[0] + 1}列,
对应转换后的第${values[2] + 1}行
第${values[3] + 1}列,
对应第${values[4] + 1}个变量`;
}
let ret2 = decode('IAAIA');
let message = desc(ret2);
console.log(ret2, message);
import _ from 'lodash';
alert(_.join(['a','b','c'],'@'));
+ new webpack.ProvidePlugin({
+ _:'lodash'
+ })
没有全局的
$
函数,所以导入依赖全局变量的插件依旧会失败
require("expose-loader?libraryName!./file.js");
{
test: require.resolve("jquery"),
loader: "expose-loader?jQuery"
}
require("expose-loader?$!jquery");
如果我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals
const jQuery = require("jquery");
import jQuery from 'jquery';
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
+ externals: {
+ jquery: 'jQuery'//如果要在浏览器中运行,那么不用添加什么前缀,默认设置就是global
+ },
module: {
+ const htmlWebpackExternalsPlugin= require('html-webpack-externals-plugin');
new htmlWebpackExternalsPlugin({
externals:[
{
module:'react',
entry:'https://cdn.bootcss.com/react/15.6.1/react.js',
global:'React'
},
{
module:'react-dom',
entry:'https://cdn.bootcss.com/react/15.6.1/react-dom.js',
global:'ReactDOM'
}
]
})
当代码发生修改后可以自动重新编译
module.exports = {
//默认false,也就是不开启
watch:true,
//只有开启监听模式时,watchOptions才有意义
watchOptions:{
//默认为空,不监听的文件或者文件夹,支持正则匹配
ignored:/node_modules/,
//监听到变化发生后会等300ms再去执行,默认300ms
aggregateTimeout:300,
//判断文件是否发生变化是通过不停的询问文件系统指定议是有变化实现的,默认每秒问1000次
poll:1000
}
}
aggregateTimeout
配置+ new webpack.BannerPlugin('珠峰培训'),
有时项目中没有引用的文件也需要打包到目标目录
npm i copy-webpack-plugin -D
new CopyWebpackPlugin([{
from: path.resolve(__dirname,'src/assets'),//静态资源目录源地址
to:path.resolve(__dirname,'dist/assets') //目标地址,相对于output的path目录
}])
npm i clean-webpack-plugin -D
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
plugins:[
new CleanWebpackPlugin({cleanOnceBeforeBuildPatterns: ['**/*', '!static-files*'],})
]
如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。
proxy: {
"/api": 'http://localhost:3000'
}
proxy: {
"/api": {
target: 'http://localhost:3000',
pathRewrite:{"^/api":""}
}
}
before 在 webpack-dev-server 静态资源中间件处理之前,可以用于拦截部分请求返回特定内容,或者实现简单的数据 mock。
before(app){
app.get('/api/users', function(req, res) {
res.json([{id:1,name:'zfpx1'}])
})
}
webpack-dev-middleware就是在 Express 中提供 webpack-dev-server 静态服务能力的一个中间件
npm install webpack-dev-middleware --save-dev
const express = require('express');
const app = express();
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackOptions = require('./webpack.config');
webpackOptions.mode = 'development';
const compiler = webpack(webpackOptions);
app.use(webpackDevMiddleware(compiler, {}));
app.listen(3000);
webpack-dev-middleware
的好处是可以在既有的 Express 代码基础上快速添加 webpack-dev-server 的功能,同时利用 Express 来根据需要添加更多的功能,如 mock 服务、代理 API 请求等指定extension之后可以不用在require
或是import
的时候加文件扩展名,会依次尝试添加扩展名进行匹配
resolve: {
extensions: [".js",".jsx",".json",".css"]
},
配置别名可以加快webpack查找模块的速度
bootstrap
,而不需要从node_modules
文件夹中按模块的查找规则查找const bootstrap = path.resolve(__dirname,'node_modules/_bootstrap@3.3.7@bootstrap/dist/css/bootstrap.css');
resolve: {
+ alias:{
+ "bootstrap":bootstrap
+ }
},
node_modules
目录resolve.modules
字段进行配置的
默认配置resolve: {
modules: ['node_modules'],
}
如果可以确定项目内所有的第三方依赖模块都是在项目根目录下的 node_modules 中的话resolve: {
modules: [path.resolve(__dirname, 'node_modules')],
}
默认情况下package.json 文件则按照文件中 main 字段的文件名来查找文件
resolve: {
// 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
mainFields: ['browser', 'module', 'main'],
// target 的值为其他时,mainFields 默认值为:
mainFields: ["module", "main"],
}
当目录下没有 package.json 文件时,我们说会默认使用目录下的 index.js 这个文件,其实这个也是可以配置的
resolve: {
mainFiles: ['index'], // 你可以添加其他默认使用的文件名
},
resolve.resolveLoader
用于配置解析 loader 时的 resolve 配置,默认的配置:
module.exports = {
resolveLoader: {
modules: [ 'node_modules' ],
extensions: [ '.js', '.json' ],
mainFields: [ 'loader', 'main' ]
}
};
module.noParse
字段,可以用于配置哪些模块文件的内容不需要进行解析module.exports = {
// ...
module: {
noParse: /jquery|lodash/, // 正则表达式
// 或者使用函数
noParse(content) {
return /jquery|lodash/.test(content)
},
}
}...
使用 noParse 进行忽略的模块文件中不能使用 import、require、define 等导入机制
DefinePlugin
创建一些在编译时可以配置的全局常量
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: "1",
EXPRESSION: "1+2",
COPYRIGHT: {
AUTHOR: JSON.stringify("珠峰培训")
}
})
console.log(PRODUCTION);
console.log(VERSION);
console.log(EXPRESSION);
console.log(COPYRIGHT);
IgnorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去
import moment from 'moment';
console.log(moment);
new webpack.IgnorePlugin(/^\.\/locale/,/moment$/)
mode
的概念npm install --save-dev optimize-css-assets-webpack-plugin
"scripts": {
+ "dev": "webpack-dev-server --env=development --open"
},
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports=(env,argv) => ({
optimization: {
minimizer: argv.mode == 'production'?[
new TerserWebpackPlugin({
parallel:true,//开启多进程并行压缩
cache:true//开启缓存
}),
new OptimizeCssAssetsWebpackPlugin({})
]:[]
}
})
export default function log(...args) {
if (process.env.NODE_ENV == 'development') {
console.log.apply(console,args);
}
}
可以把 webpack 的配置按照不同的环境拆分成多个文件,运行时直接根据环境变量加载对应的配置即可
const { smart } = require('webpack-merge')
const webpack = require('webpack')
const base = require('./webpack.base.js')
module.exports = smart(base, {
module: {
rules: [],
}
})
image-webpack-loader可以帮助我们对图片进行压缩和优化
npm install image-webpack-loader --save-dev
{
test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
use: [
'file-loader',
+ {
+ loader: 'image-webpack-loader',
+ options: {
+ mozjpeg: {
+ progressive: true,
+ quality: 65
+ },
+ optipng: {
+ enabled: false,
+ },
+ pngquant: {
+ quality: '65-90',
+ speed: 4
+ },
+ gifsicle: {
+ interlaced: false,
+ },
+ webp: {
+ quality: 75
+ }
+ }
+ },
]
}
const path=require('path');
const HtmlWebpackPlugin=require('html-webpack-plugin');
const htmlWebpackPlugins=[];
const glob = require('glob');
const entry={};
const entryFiles = glob.sync('./src/**/index.js');
entryFiles.forEach((entryFile,index)=>{
let entryName = path.dirname(entryFile).split('/').pop();
entry[entryName]=entryFile;
htmlWebpackPlugins.push(new HtmlWebpackPlugin({
template:`./src/${entryName}/index.html`,
filename:`${entryName}/index.html`,
chunks:[entryName],
inject:true,
minify:{
html5:true,
collapseWhitespace:true,
preserveLineBreaks:false,
minifyCSS:true,
minifyJS:true,
removeComments:false
}
}));
});
module.exports={
entry,
plugins: [
//other plugins
...htmlWebpackPlugins
]
}
预设 | 替代 | 描述 |
---|---|---|
errors-only | none | 只在错误时输出 |
minimal | none | 发生错误和新的编译时输出 |
none | false | 没有输出 |
normal | true | 标准输出 |
verbose | none | 全部输出 |
cnpm i friendly-errors-webpack-plugin
+ stats:'verbose',
plugins:[
+ new FriendlyErrorsWebpackPlugin()
]
编译完成后可以通过
echo $?
获取错误码,0为成功,非0为失败
"scripts": {
"build": "webpack",
+ "build:stats":"webpack --json > stats.json",
"dev": "webpack-dev-server --open"
},
const webpack = require('webpack');
const config = require('./webpack.config.js');
webpack(config,(err,stats)=>{
if(err){
console.log(err);
}
if(stats.hasErrors()){
return console.error(stats.toString("errors-only"));
}
console.log(stats);
});
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports =smw.wrap({
});
cnpm i webpack-bundle-analyzer -D
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
plugins: [
new BundleAnalyzerPlugin() // 使用默认配置
// 默认配置的具体配置项
// new BundleAnalyzerPlugin({
// analyzerMode: 'server',
// analyzerHost: '127.0.0.1',
// analyzerPort: '8888',
// reportFilename: 'report.html',
// defaultSizes: 'parsed',
// openAnalyzer: true,
// generateStatsFile: false,
// statsFilename: 'stats.json',
// statsOptions: null,
// excludeAssets: null,
// logLevel: info
// })
]
}
{
"scripts": {
"dev": "webpack --config webpack.dev.js --progress"
}
}
webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
generateStatsFile: true, // 是否生成stats.json文件
}),
]
}
{
"scripts": {
"generateAnalyzFile": "webpack --profile --json > stats.json", // 生成分析文件
"analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 启动展示打包报告的http服务器
}
}
npm run generateAnalyzFile
npm run analyz
babel-polyfill用正确的姿势安装之后,引用方式有三种:
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到它们
output.library
配置导出库的名称output.libraryExport
配置要导出的模块中哪些子模块需要被导出。 它只有在 output.libraryTarget 被设置成 commonjs 或者 commonjs2 时使用才有意义output.libraryTarget
配置以何种方式导出库,是字符串的枚举类型,支持以下配置libraryTarget | 使用者的引入方式 | 使用者提供给被使用者的模块的方式 |
---|---|---|
var | 只能以script标签的形式引入我们的库 | 只能以全局变量的形式提供这些被依赖的模块 |
commonjs | 只能按照commonjs的规范引入我们的库 | 被依赖模块需要按照commonjs规范引入 |
amd | 只能按amd规范引入 | 被依赖的模块需要按照amd规范引入 |
umd | 可以用script、commonjs、amd引入 | 按对应的方式引入 |
编写的库将通过var
被赋值给通过library
指定名称的变量。
module.exports = {
add(a,b) {
return a+b;
}
}
var calculator=(function (modules) {}({})
<script src="bundle.js"></script>
<script>
let ret = calculator.add(1,2);
console.log(ret);
</script>
编写的库将通过 CommonJS 规范导出。
exports["calculator"] = (function (modules) {}({})
require('npm-name')['calculator'].add(1,2);
npm-name是指模块发布到 Npm 代码仓库时的名称
编写的库将通过 CommonJS 规范导出。
module.exports = (function (modules) {}({})
require('npm-name').add();
在 output.libraryTarget 为 commonjs2 时,配置 output.library 将没有意义。
编写的库将通过 this 被赋值给通过 library 指定的名称,输出和使用的代码如下:
this["calculator"]= (function (modules) {}({})
this.calculator.add();
编写的库将通过 window 被赋值给通过 library 指定的名称,即把库挂载到 window 上,输出和使用的代码如下:
window["calculator"]= (function (modules) {}({})
window.calculator.add();
编写的库将通过 global 被赋值给通过 library 指定的名称,即把库挂载到 global 上,输出和使用的代码如下:
global["calculator"]= (function (modules) {}({})
global.calculator.add();
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports['MyLibrary'] = factory();
else
root['MyLibrary'] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return _entry_return_;
});
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode:'none',
entry:{
'zhufengmath':'./src/index.js',
'zhufengmath.min':'./src/index.js'
},
optimization:{
minimize:true,
minimizer:[
//可以支持es6,默认的使用TerserPlugin
new TerserPlugin({
include:/\.min\.js/
})
]
},
output:{
filename:'[name].js',
library:'zhufengmath',//配置导出库的名称
libraryExport:'default',
libraryTarget:'umd'//配置以何种方式导出库,是字符串的枚举类型
}
};
"scripts": {
+ "build": "webpack",
//zhufengnodejs zhufengjiagou
if(process.env.NODE_ENV == 'production'){
module.exports = require('./dist/zhufengmath.min.js');
}else{
module.exports = require('./dist/zhufengmath.js');
}
export function add(a,b){
return a+b;
}
export function minus(a,b){
return a-b;
}
export function multiply(a,b){
return a*b;
}
export function divide(a,b){
return a/b;
}
export default {
add,minus,multiply,divide
}
统一团队的git commit 标准
cnpm i commitizen validate-commit-msg conventional-changelog-cli -S
commitizen init cz-conventional-changelog --save --save-exact
git cz
<type>(<scope>):<subject/>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
类型 | 含义 |
---|---|
feat | 新增feature |
fix | 修复bug |
docs | 仅仅修改了文档,比如README、CHANGELOG、CONTRIBUTE等 |
style | 仅仅修改了空格、格式缩进、偏好等信息,不改变代码逻辑 |
refactor | 代码重构,没有新增功能或修复bug |
perf | 优化相关,提升了性能和体验 |
test | 测试用例,包括单元测试和集成测试 |
chore | 改变构建流程,或者添加了依赖库和工具 |
revert | 回滚到上一个版本 |
ci | CI 配置,脚本文件等更新 |
cnpm i husky validate-commit-msg conventional-changelog-cli --save-dev
"scripts": {
"commitmsg": "validate-commit-msg",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}
conventional-changelog-cli
默认推荐的 commit 标准是来自angular项目$ conventional-changelog -p angular -i CHANGELOG.md -s
-i CHANGELOG.md
表示从 CHANGELOG.md
读取 changelog
changelog
为同一文件如果你想生成之前所有 commit 信息产生的 changelog 则需要使用这条命令
conventional-changelog -p angular -i CHANGELOG.md -s -r 0
x.y.z
x
: 重大升级,做了不兼容的API修改y
: 做了向下的兼容的功能新增z
: 做了向下兼容和问题修复npm version
npm version patch 升级补丁版本号
npm version minor 升级小版本号
npm version major 升级大版本号
npm adduser
npm login
npm publish
UMD=ES Module + CJS + AMD CDN
<script src="zhufengmath.js"></script>
<script>
console.log(window.zhufengmath);
</script>
font-size
值cnpm i px2rem-loader lib-flexible -D
index.html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>主页</title>
<script>
let docEle = document.documentElement;
function setRemUnit () {
//750/10=75 375/10=37.5
docEle.style.fontSize = docEle.clientWidth / 10 + 'px';
}
setRemUnit();
window.addEventListener('resize', setRemUnit);
</script>
</head>
<body>
<div id="root"></div>
</body>
*{
padding: 0;
margin: 0;
}
#root{
width:375px;
height:375px;
border:1px solid red;
box-sizing: border-box;
}
{
test:/\.css$/,//如果要require或import的文件是css的文件的话
//从右向左处理CSS文件,loader是一个函数
use:[{
loader:MiniCssExtractPlugin.loader,
options:{
publicPath: (resourcePath, context) => {
return '/';
}
//publicPath: '/'
}
},{
loader:'css-loader',
options:{
//Enables/Disables or setups number of loaders applied before CSS loader.
importLoaders:0
}
},{
loader:'postcss-loader',
options:{
plugins:[
require('autoprefixer')
]
}
},{
+ loader:'px2rem-loader',
+ options:{
+ remUnit:75,
+ remPrecesion:8
+ }
+ }]
+ },
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins:[
new HtmlWebpackPlugin({
title: "实现 Html、JavaScript、CSS 内联",
template:"./src/index.html",//指定首页模板
}),
]
};
cnpm install raw-loader --save-dev
<!-- 内联html -->
${require("raw-loader!./inline.html").default}
<!-- 内联js -->
<script>
${require("raw-loader!./inline.js").default}
</script>
<!-- 内联css -->
<style>
${require("!!raw-loader!./inline.css").default}
</style>