webpack4那点事
本地安装
npm install --save-dev webpack
npm install --save-dev webpack@<version>
不推荐 全局安装
npm install -g webpack
预热 熟悉 common.js AMD 模块化 方案
打包JS
webpack app.js <打包后的js Name>
webpack --config webpack.conf.js
Babel
- es6/es7 Babel 为了使一些浏览器做到兼容 需要进行编译到es5语法
Babel
Babel-presets
Babel-publu
Bable-loader
webpack4.28 版本的 es6/es7
npm install -D babel-loader babel/core babel/preset-env webpack
Bable Presrt 规范的一个总结
cle
通过 targets 指定一些目标
can I use
这部分配置还挺复杂,各个环境的差别化
这个暂过 回头在找错 处理
Babel Presets 打包的规范语法的版本 babel已经集成好的插件集 es2015 es2016
cnpm install babel-polyfill babel-runtime --save
Babel Polyfill 针对函数和 方法 垫片 语法转义
Babel Runtime Transform 局部垫片 为框架准备的
cnpm install babel-plugin-transform-runtime --save-dev
typrsript-loader
安装
npm i typescipt ts-loader --save-dev
cnpm i typescipt awesome-typescript-loader --save-dev
配置
tsconfig.jso
babel-loader的配置 使用
module.exports = {
entry: { // 入口
app: './app.js'
},
output: { //
filename: '[name].[hash:8].js'
},
mode:"development",
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env',{
targets: ['> 1%', 'last 2 versions']
}]
]
}
}
}
]
}
}
- ues 可以说是单独的loader 还可以是接受对性
- Presets targrts 可以指定node 或者浏览器版本
- browserslist can I use 浏览器的支持
babel polyfill Babel Runtime Transform
函数和方法 能够处理 Genterator set
Map Array.from Array.prototype.includes
babel polyfill 全局垫片 为应用准备
Babel Runtime Transform 局部垫片 为开发框架准备 例如(vue, react)
npm install babel-polyfill babel-runtime --save npm install babel-polyfill babel-transform-runtime --save-dev
Typescript
- js的超集
- 来自微软
typesript-loader 安装
npm i typesript typesript-loader --save-dev npm i typesript awesome-typesript-loader --save-dev //更快 npm i ts-loader lodash 同时使用 js
配置 tsconfig
配置选项
常用选项
声明文件: 能够检测错误报告
` @types/lodash `
` @typrs/vue `
Typings
` npm install Typings`
` typing install lodash`
提取公用代码
- webpack4打包多页面应用过程中的提取公共代码部分。相比于webpack3,4.0版本用optimization.splitChunks配置替换了3.0版本的CommonsChunkPlugin插件。在使用和配置上,更加方便和清晰。
- 减少代码的冗余
- 提高加载速度
- CommonsChunkPlugin
- webpack.optimize.CommonsChunkPlugin
配置 plugins:
- options.name 名称 提取的
- filename 表示公用代码的打包名
- minChunks 位数字是 提取的条件 可以为特殊值 或函数
- options.chunks 提取公共代码的范围
- chidren
let webpack = require('webpack')
let path = require('path')
module.exports = {
// 多页面应用
entry: {
'pageA': './src/pageA.js',
'pageB': './src/pageB.js',
'vendor': ['lodash']
},
output: {
path: path.resolve(__dirname, './dist'), //当前运行下//制定一个目录下
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js'
},
plugins: [],
optimization: {
splitChunks: {
cacheGroups: {
// 注意: priority属性
// 其次: 打包业务中公共代码
commons: {
name: "commons",
chunks: "initial",
minSize: 1,
priority: 0
},
// 首先: 打包node_modules中的文件
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
priority: 10
}
}
}
},
}
代码分割和懒加载
两种方式实现
webpack methodes
ES 2015Loader spec
webpack4打包单页应用过程中的代码分割和代码懒加载。不同于多页面应用的提取公共代码,单页面的代码分割和懒加载不是通过webpack配置来实现的,而是通过webpack的写法和内置函数实现的。
目前:
特点: 优化加载.
分离业务代码 和 第三方依赖
分离业务代码和业务公共代码和第三方依赖
分离首次加载和访问后加载的代码
动态import
处理css style-loader和css-loader
- style-loader
- style-loader/url
- style-loader/useable
- 配置项 options
- (insertAt) 插入位置
- insertInto (插入到dom)
- singleton (是否只使用一个style标签)
- transform (转化, 浏览器环境下, 插入页面前)
const path = require('path')
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test:/\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
}
]
}
]
}
}
const path = require('path')
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
//配置一个rules(规则),rules是一个数组,里面包含一条一条的规则
rules: [
{
test:/\.css$/,
use: [
{
loader: 'style-loader',
options: {
insertInto: '#app',
singleton: true,
transform: './css.transform.js'
}
},
{
loader: 'css-loader'
}
]
}
]
}
}
css-loader
- options 配置项
- alias (解析的别名)
- importloader( @import )
- Minimize( 是否压缩 )
- modules ( 启用css-modules )
:local :global compose compose ... from path
alias
| 名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| alias | {Object} | {} | 创建别名更容易导入一些模块 |
- 已知: 图片放在 /static 目录下;
- 已知:不确认css文件放在哪里(因为模块化,方便移动,所以可1.能更改模块的目录结构);
- 需求:我想要确保我的css文件必然能引用到这个图片,即使更改1.模块的文件路径,也不影响(不需要我二次去修改);
- 行动:那么添加 css-loader 的属性,设置如下:alias: 1. {'@': __dirname + '/static/'} ;
- 行动:在css文件里,图片如下引用 background: url1.(~@/logo.png);
- 结果:我就可以确保必然css文件必然能引用到这个图片了;
- 注意: @ 前要加 ~ 让 webpack 识别(~ 是 webpack 负责识1.别,认为是根目录,而 @ 是 css-loader 负责);
module: {
rules: [
{
test:/\.css$/,
use: [
{
loader: 'style-loader',
options: {
singleton: true,
transform: './css.transform.js'
}
},
{
loader: 'css-loader',
options: {
// minimize: true,
modules: true,
// 定位到打包的位置
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
}
]
}
]
}
配置less/sass
安装: npm install less-loader less --save-dev npm install sass-loader node-sass --save-dev
webpack4.0 day01
webpack 可以进行0配置
- 打包工具 -> 输出后的结果(js模块)
- 打包(支持我们js的模块化)
手动配置webpack
默认配置文件的 webpack.config.js
新的运行命令 npx webpack 进行打包
启动本地服务 启动项目
- 内置服务插件 安装: yarn add webpack-dev-server -D
// 开启devserver
devServer: { // 开发环境 服务的配置
port: 3000,
progress: true, // 打开压缩进度条
contentBase: './dist', // 服务进程 指定启动文件夹
compress: true // ... 压缩
},
- 启动指定的html htmlwebpackPlugin
- 配置 Plugins: [ // 数组 放着所有的webpack插件]
- css loader
解析css模块
- module 模块的处理 一般是针对打包文件内部的如js文件 css 文件
module: { // 模块
// loader
rules: [ // 规则 css-loader 解析 @import这种语法
// style-loader 他是把css 插入header标签中
// loader 的特点 希望单一
// loader 用法字符串只用一个loader
// 多个loader需要 []
// loader的顺序 默认从右边向左执行 从上到下 顺序按照一定的规则
// loader 还可以写成对象方式
{
// 可以处理less
test: /\.css$/, use: [
{
loader: 'style-loader',
options: {
insertAt: 'top'
}
}, 'css-loader']
},
{
// 可以处理less || sass styles -> node-sass sass-loader styles
test: /\.less$/,
use: [
{
loader: 'style-loader',
options: {
insertAt: 'top'
}
},
'css-loader', /// 解析@import 语法 或者路径
'less-loader' // 把less->处理成css文件
]
}
]
}
抽离css样式的 loader 插件 yarn add mini-css-extract-plugin -D
浏览器加前缀 postcss-loader autoprefixer
- 配置文件 postcss.config.js
压缩css js
- optimize-css-assets-webpack-plugin
- terser-webpack-plugin
optimization:{ //优化项
// minimizer
minimizer: [
new TerserJSPlugin({}),
new OptimizeCss({})
],
},
babel-loader @babel/core @babel/preset-env
@babel/plugin-proposal-class-properties 一个类的es7语法支持
内置的api 高级语法 例如gen* primose函数 @babel/plugin-transform-runtime开发依赖 && 上线依赖 @babel/runtime
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: { // 用bable-loader 需要 把es6 转es5
presets:[
'@babel/preset-env'
],
plugins: [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
"@babel/plugin-transform-runtime",
]
}
},
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/
},
实例上的高级语法 未被解析 使用 @babel/polyfill
js 检验 eslint
{ // loader 默认 是从右向左 从上到下
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
enforce: 'pre' // previous post
}
},
},
三方依赖 jQuery
// export-loader 暴露全局 的 loader 内联loader // pre 前面执行的loader normal 普通的loader 内联loader 后置 postloader
- 全局的暴露 import $ from 'expose-loader?$!jquery'
- 二 配置文件中
1 expose-loader 暴露到window 2 ProvidePlugin 给每个模块提供一个$ 3 引入不打包的方式
图片引入的几种方式
// webpack 打包我们的图片 // 1 在js中创建图片来引入 // file-loader 默认会在内部生成一张图片 到build目录下 // 把生成的图片名字返回回来 import './index.css' import favicon from './favicon.ico' // 把图片引入 返回的结果是一个新的图片地址
let image = new Image(); console.log(favicon) image.src = favicon ; // 就是普通的字符串 document.body.appendChild(image)
// 2 在css引入 background('url)
// 3
html-withimg-loader -D html内的图片解决方式 注: 暂未解决
url-laoder
增加 cdn 的引入 选项中 配置 publicPath: 'http://www.zhztest.club'
12 打包多页
chunks: ['home']
module.exports = {
// 多入口
mode: 'development', // 模式 默认两种模式 production development
entry: {
home: './src/index.js',
other: './src/other.js'
},
output: {
// [name] home, other
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'home.html',
chunks: ['home']
}),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'other.html',
chunks: ['other', 'home']
})
]
}
配置 soure-map
// 主要 定位错误
// 1) 源码映射 会单独生成一个 sourcemap 文件 出错了 会表示 列和行 大 和 全
// 2) 不会产生单独的文件 但可以显示行和列
// devtool: 'source-map', // 增加映射文件 可以帮我们调试源码文件
// devtool: 'eval-source-map',不会产生单独的文件 但是可以显示行和列
// 3) 不会产生列 但是是一个单独的映射文件 产生后保留起来 用来后面的调试
// devtool: 'cheap-module-source-map',
// 4 ) 不会产生文件 集成在打包后的文件中 不会产生列
// devtool: 'cheap-module-eval-source-map',
devtool: 'cheap-module-eval-source-map',
watch的用法
- 实时监测打包文件的变化
watch: true,
watchOptions: { // 监控的选项
poll: 1000, // 每秒 问我 1000次
aggregateTimeout: 500, // 防抖 输入代码检测
ignored: /node_modules/ // 不需要进行监控那个文件
},
webpack小插件应用
- 1 cleanWebpackPlugin
- 2 copyWebpackPlugin
- 3 bannerPlugin (webpack)内置
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'home.html',
}),
new CopyWebpackPlugin([
{from: './doc', to:'./'}
]),
new webpack.BannerPlugin('make 2019 by zhz')
]
webpack 跨域问题
// 第一种
devServer: {
proxy: { // 重写的方式 把请求代理到服务器
'/api': {
target: 'http://localhost:3000', // 配置代理
pathRewrite: { '/api':''}
}
}
// 2 单纯模拟数据
before(app){ // 提供的方法
app.get('/user', (req, res) => {
res.json({name: 'zhz-before'})
})
}
},
//server.js // 3 安装 中间件 webpack-dev-middleware
// express
let express = require('express')
let app = express();
let webpack = require('webpack')
// 中间件
let middle = require('webpack-dev-middleware');
let config = require('./webpack.config.js')
let compiler = webpack(config)
app.use(middle(compiler))
app.get('/user', (req, res) => {
res.json({name: 'zhz'})
})
app.listen(3000)
resolve 属性的配置
resolve: { // 解析 第三包 common
modules:[path.resolve('node_modules')],
alias: {// 别名 vue vue.runtime
bootstrap: 'bootstrap/dist/css/bootstrap.css'
},
mainFields: ['style', 'main'],
// mainFiles 入口文件的名字
//如果导入文件时未带后缀 为导入的文件配置默认选项
extensions: ['.js', '.css', '.json', '.vue']
},
定义环境变量
// 内置
new webpack.DefinePlugin({
DEV: JSON.stringify('production'),
FLAG: 'true',
EXPRESSION: 1+1
}),
区分不同的环境
- 文件区分开
- base 通用配置
- wepack.dev.js 开发
- webpack.prod.js 生产环境
通过webpack-merge插件 区别环境之间打包
let { smart } = require('webpack-merge')
let base = require('./webpack.base.js')
module.exports =smart(base,{
mode: 'production'
})
noParse 优化相关
- 可以忽略一些打包项
module: {
noParse: /jquery/, // 不去解析jquery中的依赖库
lgnorePlugin
- exclude 排除 include 包含
- moment 时间插件 github
exclude: /node_modules/,
include: path.resolve('src'),
- webpack 内置插件
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
// 对象的写法
new webpack.IgnorePlugin(
{
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/
}
);
dllPlugin 动态链接库
output: {
filename: '[name].js', // 产生的文件名
path: path.resolve(__dirname, 'dist'),
library: 'ab', // 打包后 赋这个变量
libraryTarget: 'commonjs' // 以什么样的模式 commonjs var this.....
}
// webpack.config.react.js
let path = require('path');
let webpack = require('webpack')
module.exports = {
mode: 'development',
entry: {
react: ['react', 'react-dom']
},
output: {
filename: '_dll_[name].js', // 产生的文件名
path: path.resolve(__dirname, 'dist'),
library: '_dll_[name]',
// libraryTarget: 'commonjs'
},
plugins: [
new webpack.DllPlugin({ // name == library
name: '_dll_[name]',
path: path.resolve(__dirname, 'dist', 'manifest.json')
})
]
}
// webpack.config.js
// 这就是提取插件固定的部分 不需要频繁打包出来
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, 'dist', 'manifest.json')
}),
模块 happypack 可以使用多线程打包
noParse: /jquery/, // 不去解析jquery中的依赖库
rules: [
{
test: /\.js$/,
use: 'happypack/loader?id=js',
exclude: /node_modules/,
include: path.resolve('src'),
},
{
test: /\.css$/,
use: 'happypack/loader?id=css',
}
]
//
plugins: [
new Happypack({
id: 'css',
loader: ['style-loader', 'css-loader']
}),
new Happypack({
id: 'js',
use: [{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}]
}),
webpack 自带优化
- three-shaking 把没用到的代码 删除掉
- // scope-hosting 作用域提升 webpack 自动省略 可以简化的代码
抽离公共代码
optimization: {
// 分割代码块
splitChunks: {
cacheGroups: {
//公用模块抽离
common: {
chunks: 'initial',
minSize: 0, //大于0个字节
minChunks: 2, //在分割之前,这个代码块最小应该被引用的次数
},
//第三方库抽离
vendor: {
priority: 1, //权重
test: /node_modules/,
chunks: 'initial',
minSize: 0, //大于0个字节
minChunks: 2, //在分割之前,这个代码块最小应该被引用的次数
}
}
}
},
懒加载 注释: import语法已经支持
热更新
plugins: [
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new webpack.NamedModulesPlugin(), //打印更新的模块路径
new webpack.HotModuleReplacementPlugin() //热更新插件
]
// 使用
if(module.hot){
module.hot.accept('./source.js', ()=>{
let str = require('./source')
console.log(str)
})
}
tapable介绍
Webpack 核心模块 tapable 解析(转)
原文出自:https://www.pandashen.com
- 前言
- Webpack 是一个现代 JavaScript 应用程序的静态模块打包器,是对前端项目实现自动化和优化必不可少的工具,Webpack 的 loader(加载器)和 plugin(插件)是由 Webpack 开发者和社区开发者共同贡献的,而目前又没有比较系统的开发文档,想写加载器和插件必须要懂 Webpack 的原理,即看懂 Webpack 的源码,tapable 则是 Webpack 依赖的核心库,可以说不懂 tapable 就看不懂 Webpack 源码,所以本篇会对 tapable 提供的类进行解析和模拟。
实现loader
resolveLoader: {
modules: ['node_models', path.resolve(__dirname, 'loaders')]
// 别名
// alias: {
// loader1: path.resolve(__dirname, 'loader', 'loader1.js')
// }
},
module: {
rules: [
{
test: /\.js$/,
use: 'loader1'
}
]
}
配置多个loader
modules: ['node_modeles', path.resolve(__dirname, 'loaders')],
// modules: ['node_modeles', path.resolve(__dirname, 'banner')],
// alias: {
// loader1: path.resolve(__dirname, 'loader', 'loader1.js')
// }
webpack 中的插件
// DonePlugin
class DonePlugin {
apply(compiler){
compiler.hooks.done.tap('DonePlugin',(stats)=>{
console.log('编译完成');
})
}
}
module.exports =DonePlugin
// AsyncPlugin
class AsyncPlugin{
apply(compiler){
compiler.hooks.emit.tapAsync('AsyncPlugin', (compliation,cb) => {
setTimeout(() => {
console.log('文件发射出来 等一下');
cb()
}, 1000)
})
}
}
module.exports = AsyncPlugin
// webpack.config,js
let path = require('path')
let DonePlugin = require('./plugins/DonePlugin')
let AsyncPlugin = require('./plugins/AsyncPlugin')
let HtmlWebpackplugin = require(' html-webpack-plugin')
module.exports = {
mode: 'development',
entry : './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new DonePlugin(),
new AsyncPlugin()
]
}
文件列表插件
module.exports = {
mode: 'development',
entry : './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new FileLisrtPlugin({
filename: 'list.md'
})
]
}
内联webpack插件
// 把外联的标签 变成内联的标签
const HtmlWebpackPlugin = require('html-webpack-plugin');
class InlineSourcePlugin {
constructor({match}) {
this.reg = match //正则
}
processTag(tag, compilation) { //处理某一个biaoqian
let newTag,url;
console.log(this.reg)
console.log(this.reg.test)
if(tag.tagName === 'link' && this.reg.test(tag.attributes.href)){
newTag = {
tagName: 'style',
attributes: {type: 'text/css'}
}
url = tag.attributes.href
}
if(tag.tagName === 'script' && this.reg.test(tag.attributes.src)){
newTag = {
tagName: 'script',
attributes: {type: 'application/javascript'}
}
url = tag.attributes.src
}
if(url){
newTag.innerHTML = compilation.assets[url].source(); // 文件的内容 放到innner 属性上
delete compilation.assets[url]
return newTag
}
return tag;
}
processTags(data, compilation) { //处理引入标签的数据
let headTags = [];
let bodyTags = [];
data.headTags.forEach(headTag => {
headTags.push(this.processTag(headTag, compilation))
});
data.bodyTags.forEach(bodyTag => {
bodyTags.push(this.processTag(bodyTag, compilation))
});
return { ...data, headTags, bodyTags }
}
apply(compiler) {
// 要通过 webpack plugin来实现这个功能
compiler.hooks.compilation.tap('InlineSourcePlugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('alterPlugin',
(data, cb) => {
data = this.processTags(data, compilation)
cb(null, data);
})
})
}
}
module.exports = InlineSourcePlugin
打包后自动发布
let UploadPlugin = require('./plugins/UploadPlugin')
module.exports = {
mode: 'development',
entry : './src/index.js',
module: {
rules: [
{test: /\.css$/ ,use: [MiniCssExtractPlugin.loader, 'css-loader']}
]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://pw9gmzl9j.bkt.clouddn.com/'
},
plugins: [
new MiniCssExtractPlugin({
filename: 'min.css'
}),
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new FileLisrtPlugin({
filename: 'list.md'
}),
new UploadPlugin({
bucket: 'zhzwebpack',
domain: 'pw9gmzl9j.bkt.clouddn.com',
accessKey: 'saqHWhPQUDfuj7kEeMsM6Ab0avlnIqzBNreqVVCt',
secretKey: '3bM-wkA30V37W1gI4pjTyiRAuMB65V4Vla_ZnCMN',
})
]
}
// UploadPlugin
let path = require('path')
let qiniu = require('qiniu')
class UploadPlugin {
// constructor
constructor(options) {
let { bucket = '', domain = "", accessKey = '', secretKey = '' } = options
let mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
let putPolicy = new qiniu.rs.PutPolicy({ scope: bucket });
this.uploadToken = putPolicy.uploadToken(mac);
let config = new qiniu.conf.Config();
this.formUploader = new qiniu.form_up.FormUploader(config)
this.putExtra = new qiniu.form_up.PutExtra()
}
apply(compiler) {
compiler.hooks.afterEmit.tapPromise('UploadPlugin', (compiation) => {
let assets = compiation.assets;
let promises = []
Object.keys(assets).forEach(filename => {
promises.push(this.uploade(filename))
})
return Promise.all(promises)
})
}
uploade(filename) {
return new Promise((resolve, reject) => {
let readFile = path.resolve(__dirname, '../dist', filename)
this.formUploader.putFile(this.uploadToken, filename, readFile, this.putExtra, function (respErr,
respBody, respInfo) {
if (respErr) {
reject(respErr);
}
if (respInfo.statusCode == 200) {
resolve(respBody)
} else {
console.log(respInfo.statusCode);
console.log(respBody);
}
});
})
}
}
module.exports = UploadPlugin