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
const path=require('path');
module.exports={
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文件
Document
npm i webpack-dev-server –D
+ devServer:{
+ contentBase:path.resolve(__dirname,'dist'),
+ host:'localhost',
+ compress:true,
+ port:8080
+ }
+ "scripts": {
+ "build": "webpack --mode development",
+ "dev": "webpack-dev-server --open --mode development "
+ }
通过使用不同的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: {
insertAt:'top'
}
},'css-loader']
}
]
}
我们希望自动能产出HTML文件,并在里面引入产出后的资源
npm i html-webpack-plugin -D
plugins: [
+ new HtmlWebpackPlugin({
+ minify: {
+ removeAttributeQuotes:true
+ },
+ hash: true,
+ template: './src/index.html',
+ filename:'index.html'
})]
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|ttf|woff|woff2|eot)/,
use:[
{
loader:'url-loader',
options:{limit:4096}
}
]
}
还可以在CSS文件中引入图片
.logo{
width:355px;
height:133px;
background-image: url(./images/logo.png);
background-size: cover;
}
因为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']
}
npm i uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin -D
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
mode: 'development',
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,//启动缓存
parallel: true,//启动并行压缩
//如果为true的话,可以获得sourcemap
sourceMap: true // set to true if you want JS source maps
}),
//压缩css资源的
new OptimizeCSSAssetsPlugin({})
]
},
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: 'css/[name].css',
+ chunkFilename:'css/[id].css'
}),
npm i html-withimg-loader -D
index.html
webpack.config.js
{
+ test: /\.(html|htm)$/,
+ use: 'html-withimg-loader'
}
npm i less less-loader -D
npm i node-sass sass-loader -D
less
@color:orange;
.less-container{
color:@color;
}
sass
$color:green;
.sass-container{
color:green;
}
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']
},
为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀
Trident内核:主要代表为IE浏览器, 前缀为-ms
Gecko内核:主要代表为Firefox, 前缀为-moz
Presto内核:主要代表为Opera, 前缀为-o
Webkit内核:产要代表为Chrome和Safari, 前缀为-webkit
npm i postcss-loader autoprefixer -D
postcss-loader
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:{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
},
include: path.join(__dirname,'src'),
exclude:/node_modules/
}
_extend
@babel/runtime
作为一个独立模块,来避免重复引入npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
.babelrc
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": true
}
]
]
}
webpack打包的时候,会自动优化重复引入公共方法的问题
eslint
eslint-loader
configuring
babel-eslint
Rules
ESlint 语法检测配置说明
npm install eslint eslint-loader babel-eslint --D
.eslintrc.js
module.exports = {
root: true,
//指定解析器选项
parserOptions: {
sourceType: 'module'
},
//指定脚本的运行环境
env: {
browser: true,
},
// 启用的规则及其各自的错误级别
rules: {
"indent": ["error", 4],//缩进风格
"quotes": ["error", "double"],//引号类型
"semi": ["error", "always"],//关闭语句强制分号结尾
"no-console": "error",//禁止使用console
"arrow-parens": 0 //箭头函数用小括号括起来
}
}
module: {
//配置加载规则
rules: [
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: "pre",
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { fix: true } // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
},
webpack通过配置可以自动给我们source maps
文件,map
文件是一种对应编译文件和源文件的方法
devtool:'eval-source-map'
import _ from 'lodash';
alert(_.join(['a','b','c'],'@'));
+ new webpack.ProvidePlugin({
+ _:'lodash'
+ })
没有全局的
$
函数,所以导入依赖全局变量的插件依旧会失败
不需要任何其他的插件配合,只要将下面的代码添加到所有的loader之前
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';
+ externals: {
+ jquery: 'jQuery'//如果要在浏览器中运行,那么不用添加什么前缀,默认设置就是global
+ },
module: {
当代码发生修改后可以自动重新编译
watch: true,
watchOptions: {
ignored: /node_modules/, //忽略不用监听变更的目录
poll:1000, //每秒询问的文件变更的次数
aggregateTimeout: 500, //防止重复保存频繁重新编译,500毫秒内重复保存不打包
}
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
new CleanWebpackPlugin([path.resolve(__dirname,'dist')])
如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。
//请求到 /api/users 现在会被代理到请求 http://localhost:3000/api/users。
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/[email protected]@bootstrap/dist/css/bootstrap.css');
resolve: {
+ alias:{
+ "bootstrap":bootstrap
+ }
},
对于直接声明依赖名的模块(如 react ),webpack 会类似 Node.js 一样进行路径搜索,搜索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
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports=(env,argv) => ({
optimization: {
minimizer: argv.mode == 'production'?[
new UglifyJSplugin({
cache: true,//启用缓存
parallel: true,// 使用多进程运行改进编译速度
sourceMap:true//生成sourceMap映射文件
}),
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: [],
}
})
有时候我们的页面可以不止一个HTML页面,会有多个页面,所以就需要多入口
const path=require('path');
const HtmlWebpackPlugin=require('html-webpack-plugin');
module.exports={
entry: {
index: './src/index.js',
login: './src/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash].js',
publicPath: '/'
},
plugins: [
new HtmlWebpackPlugin({
minify: {
removeAttributeQuotes: true
},
hash: true,
template: './src/index.html',
chunks: ['index'],
filename: 'index.html'
}),
new HtmlWebpackPlugin({
minify: {
removeAttributeQuotes: true
},
hash: true,
chunks: ['login'],
template: './src/login.html',
filename: 'login.html'
})
],
}
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
+ }
+ }
+ },
]
}
webpack 可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。你可以使用 Node.js 来很简单地编写自己的 loader。 awesome-loaders