把每章节的代码都放到了git仓库:https://github.com/fx35792/webapck-study
不同章节的学习内容:请切到不同的分支上进行查看
第一章 导学
学习的是webpack4相关的知识。
webpack4优点:
构建速度更快了,大型项目节约90%的构建时间
内置了更多默认配置,更变了许多API
第二章 webpack初识
2-1.webpack究竟是什么?
webpack其实就是一个模块打包器。
传统制作网页引入js的方式---案例
//index.html
这是一个传统的网页
这是一个传统的网页制作
//index.js
var dom = document.getElementById('root');
var header = document.createElement('div');
header.innerHTML='header';
dom.append(header)
var slider = document.createElement('div');
slider.innerHTML='slider';
dom.append(slider)
var content = document.createElement('div');
content.innerHTML='content';
dom.append(content)
这样写最大的弊端就是:随着业务逻辑的增加,index.js会越来越大,定位某个或者某块代码的问题就要修改这个文件,也很容易导致误改或者误操作,那么我们能不能把每个功能模块拆分成为一个文件呢,要是这样的话哪个文件出问题就去找哪个文件修改即可,减低人为的误操作。所以我们把index.html和index.js进行了改良(面向对象的写法),如下:
//index.html 我们对index.js 进行业务代码或者功能模块的拆分
//拆分为了header.js slider.js content.js
这是一个传统的网页
这是一个传统的网页制作
//header.js 创建构造函数
function Header(){
var header = document.createElement('div');
header.innerHTML='header';
dom.append(header)
}
//slider.js 创建构造函数
function Slider(){
var slider = document.createElement('div');
slider.innerHTML='slider';
dom.append(slider)
}
//content.js 创建构造函数
function Content(){
var content = document.createElement('div');
content.innerHTML='content';
dom.append(content)
}
//最终的index.js
var dom = document.getElementById('root');
new Header();
new Slider();
new Content();
经过上面所述的改良完之后,我们发现:
问题一:虽然每个功能模块我们得到了拆分,但是页面上要引入多个js文件,这样一来的话就会导致增加http的请求次数,导致页面的加载速度变慢。
问题二:如果我们在index.html中更改了js文件的顺序后,也会导致一些错误在浏览器中
问题三:如果某个功能模块报错了,它首先指到的是index.js 不能准确定位到文件的位置
那么我们需要再次进行改良,改良的思路:
1、页面只引入index.js,其他的功能模块,放到index.js文件中引入
2、我们采用import 那么每个功能模块都要进行export
3、因为浏览器是不识别es6语法或者(不识别ES Module模块的引入方式),所以我们需要借助一个翻译打包工具,那就是webpack
再次改良后的代码如下:
因为需要借助webpack,所以我们要安装webpack
npm install webpack -D
npm install webpack-cli -D
//或者
npm install webpack webpack-cli -D
因为需要npm的安装,所以我们需要初识化npm init
生成package.json文件来管理(配置)我们的第三方依赖
//生成package.json文件 来管理我们的第三方依赖
npm init
//index.html
这是一个传统的网页
这是一个传统的网页制作
//index.js
import Header from './header.js'
import Slider from './slider.js'
import Content from './content.js'
new Header();
new Slider();
new Content();
//header.js
function Header(){
var dom = document.getElementById('root');
var header = document.createElement('div');
header.innerHTML='header';
dom.append(header)
}
export default Header;
//slider.js
function Slider(){
var dom = document.getElementById('root');
var slider = document.createElement('div');
slider.innerHTML='slider';
dom.append(slider)
}
export default Slider;
//content.js
function Content(){
var dom = document.getElementById('root');
var content = document.createElement('div');
content.innerHTML='content';
dom.append(content)
}
export default Content;
我们发现,在index.html中我们引入的是
为什么不是index.js?
./dist/main.js文件从哪来的?
之前我们已经提到过了,因为我们采用了es6的写法,浏览器是不能识别这种语法的,所以我们通过webpack进行了编译,最后生成了./dist/main.js文件
那么如何生成./dist/main.js文件
呢?
//打开terminal 在我们的项目目录下面执行下面命令即可,就可以生成`./dist/main.js文件`
npx webpack index.js
总结:
从这个小的例子来引出webpack,让我们初始一下webpack的小功能之一。
从这个小的例子,可以看出我们由传统的开发方式转变成了面向对象的编程方式,我们不单单的只是为了实现功能,我们正在进行工程化、模块化的来管理我们的项目和代码。
2-2.什么是模块打包工具?
从上面例子,如果你把webpack理解成为一个翻译器的话,那么是不正确。
从官网的定义来看,webpack给出的是模块打包工具。
那么webpack既然是模块打的包工具,那么支持模块规范都有哪些呢?
1.ES6 //import(导入) export(导出)
2.CommonJs //var Header = require('./header.js') (导入) module.exports = Header (导出)
3.CMD //是SeaJS在推广过程中对模块定义的规范化产出
4.AMD //define 和 require 语句 是RequireJS在推广过程中对模块定义的规范化产出
早期的webpack,只是针对js打包工具,随着webpack的发展,现在也能支持
style:css、less、scss、stylus
images:jpg、png、jpeg、gif、base64等
等进行打包
总结:
查看官网是学习webpack的必备技能之一
所以关于这小节的内容,如果想要了解和学习更多的关于模块打包知识,可以查看下面三个链接地址:
英文:https://webpack.js.org/concepts/modules/#what-is-a-webpack-module
中文:https://www.webpackjs.com/concepts/modules/
英文:https://webpack.js.org/api/module-methods/
中文:https://www.webpackjs.com/api/module-methods/
英文:https://webpack.js.org/api/module-variables/
中文:https://www.webpackjs.com/api/module-variables/
2-3.webpack的正确安装方式
建议更新本地电脑的NodeJs,因为webpack的一些新功能可能是基于新的node版本来进行开发的。
我们推荐局部安装wepack,安装到项目里面
我们不推荐全局安装webpack,因为各个项目中webpack的版本可能是不同的。
//创建package.json文件管理和配置我们第三方依赖
npm init
//or
npm init -y //会默认帮我创建package.json而不会提示我们自定义一些东西
//安装webpack webpack-cli
npm install webpack webpack-cli -D
//查看webpack的所有版本
npm view webpack versions
//查看webapck的最新版本
npm view webpack version
//or
npm info webpack
//安装指定webpack版本
npm install [email protected] -D
//检查局部或者当前项目下的webpack的安装版本
npx webpack -v
//or
npm ls webpack
//查看全局webpack的安装版本
npm ls webpack -g
如果想了解更多本章的学习内容话,还是要多查看官方文档
中文:https://www.webpackjs.com/guides/installation/
2-4.使用webpack的配置文件
1.webpack的基础配置
我们在上一个小例子中,通过npx webpack index.js
来生成编译文件的
这种方式是webpack默认配置项生成的文件
但是如果我们想要自定义编译的文件名称以及生成文件的地址,那应该如何操作呢?
那么我们就需要配置我们一个webpack的配置文件webpack.config.js
而且执行命令也要进行变更为:npx webpack
webpack会自动寻找webpack.config.js文件
//webpack.config.js
var path = require('path');
module.exports = {
entry: "./index.js",
output: {
filename: 'build.js',
path: path.resolve(__dirname,'build')
}
}
如果我们的配置文件名称不是webpack.config.js
,而是webpackconfig.js或者其他命名的js文件时,如果我们依然执行npx webpack
,那么在编译的过程中就会报错:
那么我们应该如何来编译我们自定义的配置文件呢?
npx webpack --config webpackconfig.js //自定义文件名称
2.更改一下目录结构,把源文件放到
src
的文件目录下面
再去更改webpack.config.js的入口文件
entry: "./src/index.js",
3.通过npm 命令来实现 webpack的编译
在package.json文件的scripts配置webpack的执行编译命令
"scripts": {
"build": "webpack"
},
执行命令变为了 npm run build
总结:
webpack几种打包方式:
1.全局打包global
webpack index.js
2.局部打包(本地打包) local
npx webpack index.js
3.npm scripts
npm run build
如果想了解更多本章的学习内容话,还是要多查看官方文档
英文:https://webpack.js.org/guides/getting-started/
中文:https://www.webpackjs.com/guides/getting-started/
2-5.webpack打包的输出内容
hash
:本次打包的唯一哈希标识
Version
:webpack 打包的版本号
Time
:打包所花费的时间
Build at
:什么时候进行的打包操作
Asset
:打包生成的文件名称
Size
:生成该文件的大小
Chunks
:生成文件的唯一id标识
Chunk Names
:main 主入口文件名称 entry的main的配置名称
下面的警告信息提示配置打包模式:区分生产和开发
解决的办法就是在webpack.config.js文件中配置mode属性
var path = require('path');
module.exports = {
mode: 'development',//development
// entry: "./src/index.js",
entry: {
main: "./src/index.js"
},
output: {
filename: 'build.js',
path: path.resolve(__dirname,'build')
}
}
production
:会对代码进行混淆压缩,体积变小
development
:只对文件进行编译,便于查找开发中的问题,方便调试
第3章 Webpack 的核心概念
3-1什么是loader?
loader其实就是一个打包的方案。
webpack本身是打包js文件的,但是遇到图片、样式、文件等等时,需要我们进行打包的时候,那么就需要借助其他插件来进行打包,那么除了需要安装依赖之外,还要在webpack.config.js进行配置。
下面是图片进行打包的例子:
第一安装file-loader,
npm install file-loader -D
第二配置webpack.config.js
module:{
rules:[{
test: /\.jpg$/,
use:{
loader: 'flie-loader'
}
}]
}
总结
当我们在打包的过程中,如果遇到不是js的文件,打包过程中报错,这个时候我们就要考虑打包的文件中包含哪些不是js文件,例如:图片、样式、txt、excel等等,此时我们就要去webpack的官网查阅相对应的配置loader来对我们的webpack进行laoder的配置,从而能够成功的打包和编译我们的文件。
3-2试用loader打包静态资源(图片篇)
我们在配置file-loader
中,只配置了test、use中的loader,那么在这种情况下打包出来的图是一个带有哈希值的图片。
如果我们想自定义打包出来的图片名称以及打包出来的图片路径的话,那么我们就需要对options
进行配置了。
module
: 模块
rules
:规则
test
:正则
use
:
loader
:使用什么类型的loader
options
:配置项设置
name
:定义编译文件的名称
outputPath
:定义编译文件的路径
file-loader 或者 url-loader
module:{
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader', //file-loader
options:{
//placeholder 占位符
name:'[path][name].[ext]',
outputPath:'images/',
limit:20480 //大于2kb的以文件形式存在,低于这个值的图片以base64格式写入js中
}
}
}]
}
总结
file-loader
既然能打包很多文件格式,为什么我们还要说url-loader
呢?
url-loader
有什么优点或者比较强大的地方吗?
我们知道file-loader可以将我们的图片进行打包,但是url-loader不仅可以打包图片文件,而且他可以将图片转化为base64位格式写入js里面,这样的话就可以减少http的请求。但是在这个地方我们要特别的强调一下,针对图片比较大的我们最好还是打包成图片文件,因为如果将大图片写入js文件中,那么将会导致文件特别大,这不是我们希望看到的。为了实现这个功能我们在对url-loader
进行配置的时候,设置一下图片大小上限,大过这个上限我们就以文件的形式打包,如果低于这个这个上限我们就以base64的形式打包,这样的话,既能优化小图对http的请求次数,同时也可以使得大文件图片以文件形式存在。
3-3试用loader打包静态资源(样式篇-上)
上一小节我们讲了如何针对图片文件进行webpack打包配置
这一小节我们主要讲css相关文件进行webpack的打包配置
style-loader
:将编译好的css文件挂载到当前页面的head标签的style里面
css-loader
:a.分析css文件和css文件之间的关系,b.对css进行编译
sass-loader
:支持对scss文件的编译 //要是使用sass-loader
也必须要借助node-sass
postcss-loader
:自动帮我们补全css3的一些-webkit等等的前缀 //使用它需要借助autoprefixer
npm install style-loader css-loader sass-loader node-sass postcss-loader autoprefixer -D
我们使用postcss-loader和autoprefixer两个插件的时候,可以帮我们自动加上css3的一些前缀,那么需要我们对webpack.config.js和postcss.config.js分别进行配置
postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
webpack.config.js
module:{
rules: [{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
'sass-loader',
]
}]
}
值得注意的是:
loader顺序不能随意写,有优先顺序的(从上到下,从右到左) 应该先是用autoprefixer和postcss-loader给样式添加前缀,其次采用sass-loader对scss文件进行转义为css,再采用css-loader,分析几个css之间的关系,在把这些css文件汇总合并到一个css文件中,最后style-loader再把生成css片段挂载到head里面的style中
3-4试用loader打包静态资源(样式篇-下)
1).importLoaders:2 //不管是js中引入的样式还是css中引入的样式都走postcss-loader
,sass-loader
2).modules:true //为了方式样式的全局污染,这样写话样式模块化
import './style.css'
img.classList.add('avatar');
改为:
import style from './style.css'
img.classList.add(style.avatar);
3).打包文字的时候使用:flie-loader
module:{
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',//file-loader
options:{
//placeholder 占位符
name:'[path][name].[ext]',
outputPath:'images/',
limit:1000
}
}
},{
test: /\.scss$/,
use: [
'style-loader',
// 'css-loader',
{
loader:'css-loader',
options: {
importLoaders: 2, //通过@import 引入的scss文件也会通过sass-loader和postcs-loader先处理再会来到css-loader这一步过程中
modules:true //样式模块化设置,单个页面的js文件中的样式不会影响到其他js页面中样式
}
},
'sass-loader',
'postcss-loader'
]
},{
test: /\.(eot|svg|ttf|woff|woff2)$/,
use:{
loader:'file-loader'
}
}]
}
3-5 使用 plugins 让打包更便捷
plugin插件可以在webpack在运行到某一时刻的时候,帮助做一些事情
html-webpack-plugin
:会在打包结束后,自动生成一个index.html文件,并且把打包生成的js自动引入到html中
clean-webpack-plugin
:在进行打包的时候,先把dist目录下面的所有文件进行删除
npm install html-webpack-plugin clean-webpack-plugin -D
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: 'development', //development
// entry: "./src/index.js",
entry: {
main: "./src/index.js"
},
module: {
rules: [{
test: /\.(png|jeg|gif)$/,
use: [{
loader: 'url-loader',
options: {
name: '[path][name]_[hash].[ext]',
outputPath: 'images/',
limit: 20480
}
}]
}, {
test: /\.(eot|svg|ttf|woff|woff2)$/,
use: [{
loader: 'file-loader'
}]
},
{
test: /\.(scss|css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader'
}
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
}
}
讲到这里咱们再介绍一个关于清除dist目录的npm scripts
//安装rimraf 依赖
npm install rimraf -D
//编写命令 package.json
"scripts": {
"build": "webpack",
"clean": "rimraf dist/"
},
//执行命令
npm run clean
所以关于这小节的内容,如果想要了解和学习更多的关于插件打包知识,可以查阅下面地址:
https://www.webpackjs.com/plugins/
3-6 Entry 与 Output 的基础配置
entry
:后面可以跟一个入口文件的字符串,也可以跟一个对象
output
:可以通过filename:'[name].js'设置来自动给entry设置的文件来定义输出文件名称,
如果不设置filename参数,那么main.js则是默认的文件名称会被生成
publicPath
:配置打包生成文件的的前缀地址(例如:http://www.cdn.com/main.js)
如果生成的bundle.js 或者 dist.js 会放到cdn上面,那么我们在通过html-webpack-plugin自动生成html的时候,就要把bundle.js或者dist.js文件路径上添加远程cdn地址
// entry:'./index.js',
entry:{
main:'./index.js',//会自动生成main.js
sub:'./index.js'//会自动生成sub.js 生成的名称由参数名称决定
},
output: {
// filename: 'bundle.js',
filename:'[name].js',
publicPath:'http://www.cdn.com/'
path: path.resolve(__dirname,'dist')
},
所以关于这小节的内容,如果想要了解和学习更多的关于入口和出口打包知识,可以查阅下面地址:
https://www.webpackjs.com/configuration/entry-context/
https://www.webpackjs.com/configuration/output/
https://www.webpackjs.com/guides/output-management/
3-7 SourceMap 的配置
sourceMap:
当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单地指向到 bundle.js。这中提示并没有太多帮助,因为你可能需要准确地知道错误来自于哪个源文件。
那么sourceMap就是干了一件事:他可以把打包到bundle.js文件中的某段代码第几行 映射到源文件的对应某段代码的第几行,这样就可以准确查到到问题的所在。
devtool:'cheap-module-eval-source-map' //适用于development环境
devtool:'cheap-module-source-map'//适用于production环境
source-map:会生成.map文件(bundle.js和其他js一个映射关系的一个map文件),速度比较慢
inline-source-map:也会生成map相关的代码,但是这些代码不是文件形式存在的,而是生成后放到main.js 或者bundle.js文件的末尾,速度比较慢
cheap:a.针对的是自己业务代码错误的一个校验提示,b.只提示你多少行出错了,不回提示你列出错
module:针对的是第三库错误的一个检验提示
eval:速度非常快,但是错误提示信息不会很全
3-8 使用 WebpackDevServer 提升开发效率
安装webpack-dev-server
npm install webpack-dev-server -D
1).watch
2).webpack-dev-server
优点:
检测文件更改,
自动编译打包,
自动打开默认浏览器,
访问地址一个带有http的一个请求地址(便于ajax请求,在devServer的配置项中有一个proxy属性,这个属性可以实现跨域代理)
package.json配置如下:
"scripts": {
"build": "webpack",//手动打包
"watch": "webpack --watch",//当我们去更改文件的时候,会自动帮助我们打包,不用手动再去执行命令,但是需要手动刷新页面才能看到效果,也不会帮助我们启动一个服务器
"start": "webpack-dev-server"//检测文件变化,自动编译打包,同时可以帮我们启动一个服务器
},
webpack.config.js配置如下:
devServer:{
contentBase:'./dist',//启动服务器目录地址
port: 8081,//启动的端口号
open: true//执行命令自动帮我们打开默认浏览器
},
第二种方法:手动创建一个server服务器,帮助我们去打包:
这个server服务器需要借助几个依赖express
、webpack-dev-middleware
npm install express webpack-dev-middleware -D
在项目的更目录下创一个server.js文件
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const compiler = webpack(config);
const app = express();
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}))
app.listen(3000, () => {
console.log('server is running')
})
学习更多:
https://webpack.docschina.org/guides/development/
https://www.webpackjs.com/configuration/dev-server/
https://www.webpackjs.com/configuration/devtool/
3-9 Hot Module Replacement (HMR)热模块更新
在开启了webpack-dev-server的时候,每当我们修改了代码,浏览器就会自动刷新页面,在开发的过程中,有时候我们修改了样式,我们想直接看到效果,而不希望浏览器去刷新网页。那么我就需要设置webpack,HMR闪亮登场。
//webpack.config.js
import webpack from 'webpack';
devServer:{
contentBase:'./dist',
port: 8081,
open: true,
hot: true, //需要配置
hotOnly: true////需要配置
},
plugins:[
new htmlWebpackPlugin({
template:'index.html'
}),
new cleanWebpackPlugin(['dist']),
new webpack.HotModuleReplacementPlugin() //需要配置的
]
有时候在开发过程中,我们想指定更改了的某个文件去进行热更新,而不影响其他的已经执行过的状态,那么我么应该如何操作呢?在这里我们只提一下思路
//如果number.js的某段代码进行了变更,我们只想更新这个问题那么我们就需要对这个文件进行如下的操作:
import Number from './number';
new Newber();
if(module.hot){
module.hot.accept('./number',()={
document.body.removeChild(document.getElementById('number'));
number()
})
}
学习更多
https://www.webpackjs.com/guides/hot-module-replacement/
https://www.webpackjs.com/api/hot-module-replacement/
https://www.webpackjs.com/concepts/hot-module-replacement/
3-10 使用 Babel 处理 ES6 语法
是自己编写的es6语法通过一些babel插件进行编译操作,编译成为浏览器识别的js语法
webpack和babel地址
第一步:
npm install babel-loader @babel/core -D
配置webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {//等同于 第二步操作
presets: ['@babel/preset-env']
}
}
]
}
第二步:
npm install @babel/preset-env --save-dev
配置.babelrc
{
"presets": ["@babel/preset-env"]
}
第三步:为了解决一些低版本浏览器对一个高级js语法(比如:new Promise()、map等等)的认识,还需要安装一些依赖来完善对es6语法的编译
安装依赖
npm install --save @babel/polyfill
在es6 js文件头部引入
import "@babel/polyfill";
但是如果这样操作了的话,polyfill这个插件会把很多不需要的补充语法全部给加了进来,为了解决这个问题那么就需要对webpack.config.js 再进行一些补充,
这样编译的的文件体积会变大,那么我们可以通过useBuiltIns: 'usage',来打包编译,我们开发中只用的补充语法。大大降低了打包的体积,还有就是我们可以根据浏览器的版本来是否打包补充的js语法,因为有的浏览器他本身已经支持了js的高级语法,我们再去打进去的话,那么就有点多余了。
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',//只引用必要的js补充语法
targets:{
chrome:'67' //只对大于chrome67这个版本上的进行补充,低于这个版本的话则不予处理,进而减小了打包文件的体积
}
}]
]
}
}
有时候我们需要写一些UI组件和类库那么使用@babel/polyfill就会污染到一些代码中的全局变量,为了解决这个问题我们会有另一套解决方案
注释掉import '@babel/polyfill'
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
配置如下
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//下面注释的是针对babel/polyfill 来配置的
// presets: [
// ['@babel/preset-env', {
// useBuiltIns: 'usage',
// targets:{
// chrome:'67' //只对大于chrome67这个版本上的进行补充,低于这个版本的话则不予处理,进而减小了打包文件的体积
// }
// }]
// ]
//下面这段针对的是plugin-transform-runtime 来配置的
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
}
如果options配置的内容比较多,那么就可以把options的内容放入到.babelrc文件中
.babelrc
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
3-11 Webpack 实现对React框架代码的打包
首先我们来安装react相关的依赖
npm install react react-dom -S
咱们来写一个react的js文件
// import '@babel/polyfill';
import Ract, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
render() {
return (
hello react
)
}
}
ReactDOM.render( , document.getElementById('root'))
我们在启动npm start
的时候,发现浏览器报错了,编译不识别react相关的语法。
那么我们就需要对webpack进行设置,来安装关于react依赖
npm install --save-dev @babel/preset-react
安装完依赖后,我们在.babelrc文件中进行配置
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"targets":
{
"chrome": "67"
},
}
],
["@babel/preset-react"]
]
}
学习更多:
babel-preset-react安装
第三章的总结:
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require('webpack');
module.exports = {
mode: 'development', //development
// entry: "./src/index.js",
entry: {
main: "./src/index.js"
},
devtool: 'cheap-module-eval-source-map', //development:'cheap-module-eval-source-map' production:'cheap-module-source-map'
devServer: {
contentBase: './dist',
compress: true,
port: 8081,
open: true,
hot: true, //hrm 需要配置的
hotOnly: true //hrm 需要配置的
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
//当我们配置options内容太多的时候,我们可以把这部分放入到.babelrc文件内
options: {
//1.如果是自己的业务代码可以用用preset-env
// presets: [
// [
// '@babel/preset-env',
// {
// useBuiltIns: 'usage',
// targets: {
// chrome: "67"
// },
// }
// ]
// ]
//2.如果是自己封装的ui组件那么就要使用
// plugins: [
// [
// '@babel/plugin-transform-runtime',
// {
// "corejs": 2,
// "helpers": true,
// "regenerator": true,
// "useESModules": false
// }
// ]
// ]
}
},
{
test: /\.(png|jeg|gif)$/,
use: [{
loader: 'url-loader',
options: {
name: '[path][name]_[hash].[ext]',
outputPath: 'images/',
limit: 20480
}
}]
}, {
test: /\.(eot|svg|ttf|woff|woff2)$/,
use: [{
loader: 'file-loader'
}]
},
{
test: /\.(scss|css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader'
}
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new webpack.HotModuleReplacementPlugin() //hrm 需要配置的
],
output: {
publicPath: '/',
filename: 'dist.js',//'[name]_[hash].js'
path: path.resolve(__dirname, 'dist')
}
}
//.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"targets":
{
"chrome": "67"
},
}
],
["@babel/preset-react"]
]
}
//postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
//package.json
{
"name": "webpackdemo2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server",
"watch": "webpack --watch",
"build": "webpack",
"server": "node server.js",
"clean": "rimraf dist/"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@babel/polyfill": "^7.4.4",
"@babel/runtime": "^7.5.4",
"@babel/runtime-corejs2": "^7.5.4",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.5"
},
"devDependencies": {
"@babel/core": "^7.5.4",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.0.0",
"autoprefixer": "^9.6.1",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^2.6.9",
"css-loader": "^3.0.0",
"express": "^4.17.1",
"file-loader": "^4.0.0",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.12.0",
"postcss-loader": "^3.0.0",
"rimraf": "^2.6.3",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"url-loader": "^2.0.1",
"webpack-dev-middleware": "^3.7.0",
"webpack-dev-server": "^3.7.2"
},
"browserslist": [
"last 10 Chrome versions",
"last 5 Firefox versions",
"Safari >= 6",
"ie>8"
]
}
第4章 Webpack 的核心概念
4-1 Tree Shaking 概念详解
Tree Shaking: 摇晃树,当我引入一个模块的时候,打包不引入模块中的所有的代码,只引入它需要的代码。
//math.js
export const add = (a,b) =>{
console.log(a+b)
}
export const minus = (a,b) => {
console.log(a-b)
}
//index.js
import { add } from './math.js'
add(1,2)
在没有配置Tree Shaking的情况下,我们只调用了math.js中add的方法,但是我们打包出来的文件中除了add方法还有minus方法,这样就会导致打包出来的文件比较冗余,而且不是我们期望打包出来的文件
那么如何才能实现打包的过程生成的是我们想要的文件的,没有冗余的文件呢?
//webpack.config.js 需要添加这段配置(开发环境这样配置,正式环境就不需要这段配置,因为正式环境已经帮助我们配置了)
optimization: {
useExports: true
}
//package.json
"sideEffects" :false //默认配置上
"sideEffects" :["*.css"] //实际开发中,因为我们在引入样式因为的时候没有import,所以Tree Shaking容易误伤把样式文件也给摇掉,但是这不我们想要,所以在这配置上对样式的限制
类似的还有["@babel/polyfill"]
ps: Tree Shaking 只支持 ES Module 也就是
支持:
import { add } from './math.js'
不支持:
const add = require('./math.js')
4-2 Develoment 和 Production 模式的区分打包
因为开发环境和生产环境,运行环境、配置项、输出文件内容不同。
例如:
开发环境:
我们需要启动的时候,自动帮我们打开浏览器、配置端口号
我们在修改代码的时候,需要自动刷新页面
编译后的文件,为了准确定位问题,我们不需要代码的合并和压缩
正式环境:
我们只需要保持代码的精简,需要合并压缩,没有的文件和映射不需要出现在代码中等等
为了切换环境避免不断的修改webpack的配置代码的带来的麻烦:(浪费时间和配置错处等),所以我们要对webpack.config.js进行拆分
拆分的思想:
1.把正式环境和开发环境相同的代码提取出来,放到一个公共文件中(webpack.common.js)
2.webpack.dev.js 只保留开发环境所需要的配置信息和依赖,且通过webpack-merge 引入公共代码webpack.common.js
3.webpack.prod.js 只保留正式环境所需要的配置信息和依赖,且通过webpack-merge 引入公共代码webpack.common.js
4.有的时候,会把这三个文件放到config文件或者build文件中
5.package.json修改启动命令和地址
//安装webpack-merge
npm install webpack-merge -S
//package.json
"scripts": {
"build": "webpack --config ./build/webpack.prod.js",
"watch": "webpack --watch",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"server": "node server.js"
},
//build文件下的(webpack.common.js、webpack.dev.js、webpack.prod.js)
//webpack.common.js
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
//const cleanWebpackPlugin = require('clean-webpack-plugin'); //早期版本
const { cleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader', //file-loader
options: {
//placeholder 占位符
name: '[path][name].[ext]',
outputPath: 'images/',
limit: 1000
}
}
}, {
test: /\.scss$/,
use: [
'style-loader',
// 'css-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
// modules:true
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}, {
test: /\.(eot|svg|ttf|woff|woff2)$/,
use: {
loader: 'file-loader'
}
}, {
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}]
},
plugins: [
new htmlWebpackPlugin({
template: 'src/index.html'
}),
// 早期版本 clean-webpack-plugin
//new cleanWebpackPlugin(['dist'], {
// root: path.resolve(__dirname, '../')
// })
// 新版本
new CleanWebpackPlugin({
cleanStaleWebpackAssets: true
}),
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../dist')
}
};
//webpack.dev.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map', //production: cheap-module-source-map
devServer: {
contentBase: './dist',
port: 8081,
open: true,
hot: true,
hotOnly: true
},
optimization: {
usedExports: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
module.exports = merge(commonConfig, devConfig);
//webpack.prod.js
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const prodConfig = {
mode: 'development',
devtool: 'cheap-module-source-map', //production: cheap-module-source-map
};
module.exports = merge(commonConfig, prodConfig);
4-3 Webpack 和 Code Splitting
1.为什么要进行代码分割?
因为我们在做开发的过程中,需要引入很多库文件,而这些库文件呢,我们都没有做过任何变更(比如:lodash、md5等第三方库文件)
如果我们不做代码分割,那么就会我们的业务代码和第三方库文件一起打包。这样就会导致两个问题:
1).文件过大,加载时间互变长
2).每次更新业务代码,第三方库文件也会被打包进去,没有把第三库文件缓存到浏览器中,所以加载时间依然会很长
2.通过webpack如何解决呢?
//第一种:同步代码 配置 webpack.common.js
optimization:{
splitChunks:{
chunks: 'all'
}
}
//package.json 添加开发环境打包命令
"dev-build": "webpack --config ./build/webpack.dev.js",
//npm run dev-build
会生成一个venders-main.js(第三库文件)和 业务代码文件(main.js)
//第二种:异步代码,无需任何配置,会自动进行代码分割
//index.js
function getComponet() {
return import('lodash').then(({default: _}) => {
var element = document.createElement('div');
element.innerHTML = _.join(['sunny', 'fan'], '***')
return element
})
}
getComponet().then((element) => {
document.body.appendChild(element)
});
在使用第二种异步代码进行代码分割的时候,打包的时候会报错,我们根据打包报错的指令来安装相关的依赖即可:
//安装依赖
npm install @babel/plugin-syntax-dynamic-import
//配置依赖(.babelrc)
{
"plugins":[
["@babel/plugin-syntax-dynamic-import"]
]
}
4-4 SplitChunksPlugin 配置参数详解
//index.js 这是一段异步代码调用lodash的方法
function getComponent(){
return import(/* webpackChunkName:"lodash" */'lodash').then(({default:_})=>{
var div = document.createElement('div');
div.innerHTML = _.join(['sunny','Fan'],'----')
return div
})
}
getComponent().then((value)=>{
document.body.appendChild(value)
});
npm run build
执行代码dist文件生成的vender~lodash.js会改为lodash.js文件
//webpack.common.js
optimization: {
splitChunks: {
chunks: 'async', //all(两者), async(异步), initial(同步)
minSize: 30000, // 当打包文件大于这个值则进行代码分割,反之则不进行代码分割
maxSize: 0, //
minChunks: 1, //当第三方库引用一次就打包分割,如果设置为2,引用一次则不打包分割
maxAsyncRequests: 5,//当你引入很多库文件的时候,前五个会进行拆分打包,后面的则不进行打包分割
maxInitialRequests: 3,//整个网站首页(入口文件)最多代码分割为3分
automaticNameDelimiter: '~', //生成的venders~main.js 中间连接符设置
name: true, // 支持 filename的设置
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,//同时满足venders和default的配置时,优先打包分割的权重 值越大,越优先
filename:'vender.js'
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true// a.js 引入了b.js 那么打包的时候如果已经对a.js打包分割了,那么就不会再对b.js进行打包分割了
}
}
}
}
}
学习更多:
https://webpack.js.org/plugins/split-chunks-plugin/#defaults
4-5 Lazy Loading 懒加载,Chunk 是什么?
当一个页面在初始化加载的页面的时候,我们不会把所有的文件或者代码加载到页面之中,而是当我们执行某个执行事件或者命令的时候,我们才会加载这段代码,这个过程就叫做懒加载。
比如:
当我们把业务逻辑写成通过异步来加载的时候或async await等方法
//index.js
async function getComponent() {
const { default: _ } = await import('lodash')
const element = document.createElement('div');
element.innerHTML = _.join(['sunny', 'Fan'], '-');
return element
}
document.addEventListener('click', () => {
getComponent().then((value) => {
document.body.appendChild(value)
})
})
只有当我们点击body的时候,才会在页面挂载相应的数据,这个过程就是懒加载。
那么我们为什么要在开发过程使用懒加载呢?
我们知道当业务代码少的时候,我们可以不考虑这个,但是当业务比较旁大的时候,我们不可能一次性把所有的代码一下子都加载下来,我们希望我们点击哪个页面,只加载当前页面的相关代码,可以增加我们打开每个页面的速度。
4-6 打包分析,Preloading和Prefetching
https://github.com/webpack/analyse
打包完的文件我们可以通过 webpack --profile --json > stats.json
生成一个stats.json的文件来分析我们的打包文件
http://webpack.github.com/analyse
通过这个网址,选择刚才生成的stats.json查看 相关的数据
学习更多:
https://webpack.js.org/guides/code-splitting/#bundle-analysis
从webpack打包和优化思想上来看,我们打开一个网站并不是希望通过缓存来加快我们打开网站的速度,而是通过打开网站的页面的代码利用率
如何通过chrome查看网站的利用率呢?
打开一个网址:www.baidu.com
快捷键针对的是mac笔记本:option+cmd+i
打开调试面板后再按cmd+shif+p
输入>coverge
就可以查看当前页面的文件代码利用率,随便找一个文件点击进去
红色部分是可以优化地方,而绿色部分则是当前页面使用代码
下面给一个小例子:
//index.js
document.addEventListener('click', () => {
import(/* webpackPrefetch:true */'./click.js').then(({ default: func }) => {
func()
})
})
//click.js
function handleClick() {
const element = document.createElement('div');
element.innerHTML = 'sunny fan';
document.body.appendChild(element)
}
export default handleClick;
执行npm run build
来体检下面两个不同配置加载文件的方式
webpackPrefetch
:加载页面的时候,等网络空闲的的时候再去加载相对应的代码(推荐,但是在有些浏览器上存在一些兼容问题,所以使用的时候要注意一下)
webpackPreload
:则是同文件一起同时加载,指到加载完毕为止
4-7 CSS文件代码分割
我们想去项目里面添加一个style.css文件
//style.css
background:green
//index.js
import './style.css'
npm run build
用浏览器打开dist文件下面的index.html发现没有样式
因为我们之前TreeShaking的配置,所以我们要对css过滤掉这个
//package.json
"sideEffects": [
"*.css"
],
npm run build
用浏览器打开dist文件下面的index.html就有样式了
但是我们发现,在index.html代码中我们并没有引入style.css文件,打包在dist文件下面也没有发现style.css文件,我们发现webpack把style.css文件内容打入到main.js内容当中了。
如果我们想要把css文件单独打包出来,那么我们应该如何操作呢?
//1.安装依赖
npm install --save-dev mini-css-extract-plugin
//webpack.common.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
ignoreOrder: false, // Enable
})
],
'style-loader',改为:MiniCssExtractPlugin.loader,
//如果我们想要对css样式进行打包压缩
npm install optimize-css-assets-webpack-plugin -D
//webpack.prod.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})],
},
4-8 webpack与浏览器缓存(Caching)
我们在开发过程中,当个我们开发完我们的业务代码的时候,我们进行打包的时候,如果生成的打包文件名称没有发生变更,直接上传到服务器上面,用户刷新页面因为已经缓存了久的文件名称,所以就不会从服务器上拉去最新的代码,导致看不到最新的页面或者功能,那么如果解决这个问题呢?
我们采取的是:
进行更改了文件,打包的时候加上不同的哈希值。
没有进行更改的库文件以及业务代码:打包的时候哈希值不进行变更。
这样用户在刷新页面的时候,库文件以及没有更改的业务代码不需要重新服务器端下载,只下载更改了名称了文件即可。那么应该如何操作呢?
//webpack.common.js 添加contenthash的配置、库文件打包到verder文件中
optimization: {
runtimeChunk: {
name:'runtime'
},
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors'
}
}
}
},
output: {
// publicPath: '/',
filename: '[name][contenthash].js',
chunkFilename: '[name][contenthash].js',
path: path.resolve(__dirname, '../dist')
}
4-9 Shimming(面片)的作用
Shimming是什么东东?
有时候我们在开发过程中,比如父组件已经引入了jquery
或者lodash
等等子组件中我不想再引入一个jquery
和lodash
的模块,那么如何在webpack中如何进行操作呢?
//webpack.common.js
const webpack = require('webpack');
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
_: 'lodash'
})
],
//index.js
import $ from 'jquery';
import _ from 'lodash';
import {ui} from './jquery.ui.js'
ui();
const element = $("div");
element.html(_.join(['hello', 'world'], '--'));
$('body').append(element)
console.log('hello world111');
//jquery.ui.js
// import $ from 'jquery';
// import _ from 'lodash';
export function ui() {
$('body').css('background', _.join(['blue'],''))
}
4-10 环境变量的使用方法
之前我们配置build三个文件要进行重新改写一下
以前是执行不同的文件来进行开发和正式的区分
现在执行同一个文件,通过执行变量来区分开发和正式的环境
//webpack.dev.js
module.exports = merge(commonConfig, devConfig)
改为:
module.exports = devConfig
//webpack.prod.js
module.exports = merge(commonConfig, prodConfig)
改为:
module.exports = prodConfig
//webpack.common.js
const merge = require('webpack-merge');
const devConfig = require('./webpack.dev.js');
const prodConfig = require('./webpack.prod.js');
const commonConfig = {
.....
}
module.exports = (env) => {
if (env && env.production) {
return merge(commonConfig, prodConfig)
} else {
return merge(commonConfig, devConfig)
}
}
//package.json
将scripts里面的`./build/webpack.prod.js`和`./build/webpack.dev.js`
都改为:
`./build/webpack.common.js`
在正式环境编译前面增加参数:`--env.production`
"scripts": {
"start": "webpack-dev-server --config ./build/webpack.common.js",
"watch": "webpack --watch",
"dev-build": "webpack --config ./build/webpack.common.js",
"build": "webpack --env.production --config ./build/webpack.common.js",
"analyse": "webpack --profile --json > stats.json --config ./build/webpack.common.js",
"server": "node server.js",
"clean": "rimraf dist/"
},