这是我花了几个星期学习webpack4的学习笔记。内容不够细,由于一些相对比较简单的,就随意带过了。但愿文章能给你们带来帮助。若有错误,但愿及时指出。例子都在learn-webpack仓库上。若是你从中有所收获的话,但愿你能给个人github
点个star
。javascript
当你要开发第三方库供别人使用时,就须要用到library
和libraryTarget
这两个配置了。html
library
vue
output: { filename: 'library.js', library: 'library', libraryTarget: 'umd' },
library
: 当配置了这个library
参数后,会把library
这个key
对应的value
即上面代码library
挂载到全局做用域中。html
用script
标签引入,能够经过访问全局变量library
访问到咱们本身开发的库。java
libraryTarget
:这个默认值为var
。意思就是让library定义的变量挂载到全局做用域下。固然还有浏览器环境的window
,node
环境的global
,umd
等。当设置了window
、global
,library
就会挂载到这两个对象上。当配置了umd
后,你就能够经过import
,require
等方式引入了。node
externals
react
exterals
是开发公共库很重要的一个配置。当你的公共库引入了第三方库的时候,公共库会把该第三方库也打包进你的模块里。当使用者也引入了这个第三方库后,这个时候打包就会又打了一份第三方库进来。jquery
所在在公共模块库中配置以下代码webpack
externals: { // 前面的lodash是个人库里引入的包名 好比 import _ from 'lodash' // 后面的lodash是别人业务代码须要注入到他本身模块的lodash 好比 import lodash from 'lodash',注意不能import _ from 'lodash',由于配置项写了lodash 就不能import _ lodash: 'lodash' },
前面的lodash
是个人库里引入的包名 好比 import _ from 'lodash'
,后面的lodash
是别人业务代码须要注入到他本身模块的lodash
好比 import lodash from 'lodash'
,注意不能import _ from 'lodash'
,由于配置项写了lodash
就不能import _
。ios
本人作了个试验,当本身开发的包不引入lodash
,业务代码中也不引入lodash
,那么打包业务代码的时候,webpack
会把lodash
打进你业务代码包里。git
固然externals
,配置还有多种写法,以下
externals: { lodash: { commonjs: 'lodash', commonjs2: 'lodash', amd: 'lodash', root: '_' } } externals: ['lodash', 'jquery'] externals: 'lodash'
具体请参考官网externals
学了上面的配置后,就须要学习下如何将本身的包发布到npm
仓库上了。
package.json
的入口要改为dist
目录下的js文件如: "main": "./dist/library.js"
npm login
进行登陆,或者npm adduser
添加帐号npm publish
当出现以下提示表明发布成功
// 当出现相似以下代码时,表示你已经发布成功 ➜ library git:(master) ✗ npm publish + atie-first-module-library@1.0.0
遇到的问题:
当你遇到npm ERR! you must verify your email before publishing a new package
说明你尚未激活你的邮箱,去邮箱里点击下连接激活下就ok了
当你已经登陆了提醒npm ERR! 404 unauthorized Login first
,这个时候你就要注意下你的npm
源了,看看是否设置了淘宝源等。记得设置回来npm config set registry https://registry.npmjs.org/
http-server
workbox-webpack-plugin
相信不少朋友都有耳闻过PWA
这门技术,PWA
是Progressive Web App
的英文缩写, 翻译过来就是渐进式加强WEB应用, 是Google 在2016年提出的概念,2017年落地的web技术。目的就是在移动端利用提供的标准化框架,在网页应用中实现和原生应用相近的用户体验的渐进式网页应用。
优势:
应用场景:
当你访问正常运行的服务器页面的时候,页面正常加载。可当你服务器挂了的时候,页面就没法正常加载了。
这个时候就须要使用到pwa技术了。
这里我编写最简单的代码重现下场景:
// webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin') const CleanWebpackPlugin = require('clean-webpack-plugin') module.exports = { mode: 'production', entry: './index.js', output: { filename: 'bundle.js' }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin() ] } // index.js console.log('this is outer console') // package.json { "name": "PWA", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack --config webpack.config.js", "start": "http-server ./dist" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "clean-webpack-plugin": "^2.0.1", "html-webpack-plugin": "^3.2.0", "http-server": "^0.11.1", "webpack": "^4.30.0", "webpack-cli": "^3.3.1", } }
执行下npm run build
. ├── bundle.js └── index.html
为了模拟服务器环境,咱们安装下http-server
npm i http-server -D
配置下package.json
,"start": "http-server ./dist"
执行npm run start
来启动dist文件夹下的页面
这个时候控制台会正常打印出'this is outer console'
当咱们断开http-server
服务后,在访问该页面时,页面就报404了
这个时候就须要使用到pwa技术了
使用步骤:
安装: npm i workbox-webpack-plugin -D
webpack配置文件配置:
// webpack.config.js const {GenerateSW} = require('workbox-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const CleanWebpackPlugin = require('clean-webpack-plugin') module.exports = { mode: 'production', entry: './index.js', output: { filename: 'bundle.js' }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin(), new GenerateSW({ skipWaiting: true, // 强制等待中的 Service Worker 被激活 clientsClaim: true // Service Worker 被激活后使其当即得到页面控制权 }) ] }
这里咱们写一个最简单的业务代码,在注册完pwa以后打印下内容:
// index.js console.log('this is outer console') // 进行 service-wroker 注册 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker .register('./service-worker.js') .then(registration => { console.log('====== this is inner console ======') console.log('SW registered: ', registration); }) .catch(registrationError => { console.log('SW registration failed: ', registrationError); }); }); }
执行下打包命令:npm run build
. ├── bundle.js ├── index.html ├── precache-manifest.e21ef01e9492a8310f54438fcd8b1aad.js └── service-worker.js
打包以后会生成个service-worker.js
与precache-manifest.e21ef01e9492a8310f54438fcd8b1aad.js
接下来再重启下http-server
服务:npm run start
页面将会打印出
this is outer console ====== this is inner console ====== ...
而后咱们再断开http-server
服务
刷新下页面,居然打印出了相同的代码。说明pwa离线缓存成功。
使用webpack打包ts文件,就须要安装ts-loader
安装:npm i ts-loader typescript -D
webpack.config.js
文件中添加解析typescript
代码的loader
const HtmlWebpackPlugin = require('html-webpack-plugin') const CleanWebpackPlugin = require('clean-webpack-plugin') module.exports = { mode: 'production', entry: './src/index.ts', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.ts$/, loader: 'ts-loader', exclude: /node_modules/ } ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin() ] }
配置了webpack.config.js
还不行,还得在根目录文件下新增个.tsconfig.json
文件
{ "compilerOptions": { "outDir": "./dist/", // 默认解析后的文件输出位置 "noImplicitAny": true, // 存在隐式 any 时抛错 "module": "es6", // 表示这是一个es6模块机制 "target": "es5", // 表示要讲ts代码转成es5代码 "allowJs": true // 表示容许引入js文件。TS 文件指拓展名为 .ts、.tsx 或 .d.ts 的文件。若是开启了 allowJs 选项,那 .js 和 .jsx 文件也属于 TS 文件 } }
新建index.ts
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } let greeter = new Greeter("world"); let button = document.createElement('button'); button.textContent = "Say Hello"; button.onclick = function() { alert(greeter.greet()); } document.body.appendChild(button);
执行打包命令,访问打包后的页面,页面正常执行。
当须要使用lodash
等库时,
需安装:npm i @types/lodash -D
修改页面代码 引入 lodash
import * as _ from 'lodash' class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } let greeter = new Greeter("world"); let button = document.createElement('button'); button.textContent = "Say Hello"; button.onclick = function() { alert(_.join(['lodash', greeter.greet()], '-')); } document.body.appendChild(button);
提醒:ts使用的包,可经过https://microsoft.github.io/TypeSearch
这个网址去查对应的包使用指南
WebpackDevServer
实现请求转发当咱们工做本地开发某一个需求的时候,须要将这块需求的请求地址转发到某个后端同事的本地服务器或某个服务器上,就须要用到代理。而后其余页面的请求都走测试环境的请求。那么咱们该怎样拦截某个请求,并将其转发到咱们想要转发的接口上呢?
这个时候就须要用到webpack-dev-server
主要看devServer
配置:
// webpack.config.js const CleanWebpackPlugin = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: "development", entry: './index.js', output: { filename: 'bundle.js' }, devServer: { contentBase: './dist', open: true, hot: true }, plugins: [ new HtmlWebpackPlugin(), new CleanWebpackPlugin() ] }
// package.json scripts: { "server": "webpack-dev-server" }
// index.js import axios from 'axios' const div = document.createElement('div') div.innerHTML = 'hahahha' div.addEventListener('click', () => { alert('hahah') axios.get('/list').then(res => { console.log(res) }) }) document.body.appendChild(div)
在写一个本地启动的服务端代码
const express = require('express') const app = express() app.get('/api/list', (req, res) => { res.json({ success: true }) }) app.listen(8888, () => { console.log('listening localhost:8888') })
执行npm run server
命令,浏览器会自动打开页面。点击div后,会发起请求。
浏览器提示http://localhost:8080/api/list 404 (Not Found)
,表示该接口不存在。
由于咱们webpack
启动静态资源服务器默认端口为8080,因此他求会直接请求到8080的/api/list接口。因此会提示找不到该接口。
为了解决这个问题,咱们就须要将该请求从8080端口代理到8888端口(也就是咱们本身本地启动的服务)
配置webpack.config.js
这里我只展现devServer
代码
// webpack.config.js devServer: { contentBase: './dist', open: true, hot: true, proxy: { '/api': { target: 'http://localhost:8888' } } },
配置devServer
的proxy
字段的参数,将请求/api
开头的请求都转发到http://localhost:8888
,
经过这个方法能够解决一开始提到的本地开发的时候,只想把部分接口转发到某台部署新需求的服务器上。好比当你这个项目请求不少,不一样接口部署在不一样的端口或者不一样的服务器上。那么就能够经过配置第一个路径,来区分不一样的模块。并转发到不一样的服务上。如:
// webpack.config.js devServer: { contentBase: './dist', open: true, hot: true, proxy: { '/module1': { target: 'http://localhost:8887' }, '/module2': { target: 'http://localhost:8888' }, '/module3': { target: 'http://localhost:8889' } } },
当你要代理到某个https的接口上,就须要设置secure: false
// webpack.config.js devServer: { proxy: { '/api': { target: 'https://other-server.example.com', secure: false } } }
target: '', // 代理的目标地址 secure: false, // 请求https的须要设置 changeOrigin: true, // 跨域的时候须要设置 headers: { host: 'http://www.baidu.com', //修改请求域名 cookie: '' } ...
其余关于devServer
的配置详见devServer
相信你们都是开发过vue或者react单页面带路由的应用。这里就忽略业务代码,介绍下devServer
的historyApiFallback
参数
devServer: { historyApiFallback: true, // 当设置为true时,切换路由就不会出现路由404的问题了 }
安装eslint
: npm i eslint -D
目录下新建.eslintrc.json
文件。
environment
: 指定脚本的运行环境
globals
: 脚本在执行期间访问的额外全局变量。
rules
: 启动的规则及其各自的错误级别。
解析器选项
: 解析器选项
编辑你的eslint
的规则
{ "parserOptions": { "ecmaVersion": 6, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "rules": { "semi": 2 } }
vscode
安装eslint
插件。
配置下webpack.config.js
配置。
... devServer: { overlay: true, contentBase: './dist', hot: true, open: true }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: ['eslint-loader'] }] } ...
eslint-loader
是用于检查js
代码是否符合eslint
规则。
这里devServer
中的overlay
的做用是,当你eslint报错的时候,页面会有个报错蒙层。这样就不局限于编辑器(vscode)的报错提醒了。
若是js代码使用了多个loader,那么eslint-loader必定要写在最右边。若是不写在最后一个的话,需在里面添加enforce: "pre"
,这样无论写在哪一个位置都会优先使用eslint-loader
校验下代码规范。
{ loader: 'eslint-loader', options: { enforce: "pre", } }
webpack
打包速度的方法webpack
版本 node
版本 npm
等版本loader
include
exclude
plugin
resolve
resolve: { extensions: ['.js'], alias: { 'src': path.resolve(__dirname, '../src') } }
extensions
: 可让你import模块的时候不写格式,当你不写格式的时候,webpack会默认经过extensions中的格式去相应的文件夹中找
alias
:当你import
的路径很长的时候,最好使用别名,能简化你的路径。
好比:import index.js from '../src/a/b/c/index.js'
设置别名:
resolve: { alias: { '@c': path.resolve(__dirname, '../src/a/b/c') } }
这样你的import
导入代码就能够改为import index.js from '@c/index.js'
咱们先记录下不使用dll
打包时间787ms
:
Time: 787ms Built at: 2019-05-04 18:32:29 Asset Size Chunks Chunk Names bundle.js 861 KiB main [emitted] main index.html 396 bytes [emitted]
接下来咱们就尝试使用dll
技术
咱们先配置一个用于打包dll
文件的webpack
配置文件,生成打包后的js
文件与描述动态连接库的manifest.json
// webpack.dll.config.js const path = require('path') const webpack = require('webpack') module.exports = { entry: { vendor: ['jquery', 'lodash'] // 要打包进vendor的第三方库 }, output: { filename: '[name].dll.js', // 打包后的文件名 path: path.resolve(__dirname, './dll'), // 打包后存储的位置 library: '[name]_[hash]' // 挂载到全局变量的变量名,这里要注意 这里的library必定要与DllPlugin中的name一致 }, plugins: [ new webpack.DllPlugin({ // 用于打包出一个个单独的动态连接库文件 name: '[name]_[hash]', // 引用output打包出的模块变量名,切记这里必须与output.library一致 path: path.join(__dirname, './dll', '[name].manifest.json') // 描述动态连接库的 manifest.json 文件输出时的文件名称 }) ] }
重点:这里引入的DllPlugin插件,该插件将生成一个manifest.json文件,该文件供webpack.config.js中加入的DllReferencePlugin使用,使咱们所编写的源文件能正确地访问到咱们所须要的静态资源(运行时依赖包)。
配置下package.json
文件的scripts
: "build:dll": "webpack --config webpack.dll.config.js"
执行下 npm run build:dll
Time: 548ms Built at: 2019-05-04 18:54:09 Asset Size Chunks Chunk Names vendor.dll.js 157 KiB 0 [emitted] vendor
除了打包出dll
文件以外,还得再主webpack
配置文件中引入。这里就须要使用到DllReferencePlugin
。具体配置以下:
// webpack.config.js new webpack.DllReferencePlugin({ manifest: require('./dll/vendor.manifest.json') }),
这里的manifest
:须要配置的是你dllPlugin
打包出来的manifest.json
文件。让主webpack
配置文件经过这个
描述动态连接库manifest.json
文件,让js
导入该模块的时候,直接引用dll
文件夹中打包好的模块。
看似都配置好了,接下来执行下命令 npm run build
使用dll
打包后时间:
Time: 471ms Built at: 2019-05-04 18:19:49 Asset Size Chunks Chunk Names bundle.js 6.43 KiB main [emitted] main index.html 182 bytes [emitted]
直接从最开始的787ms
下降到471ms
,当你抽离的第三方模块越多,这个效果就越明显。
浏览器跑下html
页面,会报错
Uncaught ReferenceError: vendor_e406fbc5b0a0acb4f4e9 is not defined
这是由于index.html
还须要经过script
标签引入这个dll
打包出来的js
文件
咱们若是每次本身手动引入的话会比较麻烦,若是dll
文件很是多的话,就不可思议了。
这个时候就须要借助add-asset-html-webpack-plugin
这个包了。
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin') new AddAssetHtmlPlugin({ filepath: path.resolve(__dirname, './dll/vendor.dll.js') })
经过这包,webpack
会将dll
打包出来的js
文件经过script
标签引入到index.html
文件中
这个时候你在npm run build
,访问下页面就正常了
tree-shaking 等
借助线上或者本地打包分析工具
开发环境的时候不会生成dist文件夹,会直接从内存中读取,由于内存读取比硬盘读取快