为什么要构建?
前端发展好快的。
越来越多的思想和框架都出来了。
就是为了提高开发的效率。
比如ES6需要通过babel转成ES5才能在浏览器上面运行吧。
比如SCSS需要转换成css才能在浏览器运行吧
…
这些思想和框架,都是有构建需求的。
为什么要选择webpack来做构建?
webpack 把一切都当成模块!
webpack可以通过plugin来拓展功能!
webpack的社区非常的庞大活跃,紧跟着时代!生态链完整,维护性也很高!
想要理解为什么要使用 webpack,我们先回顾下历史,在打包工具出现之前,我们是如何在 web 中使用 JavaScript 的。 在浏览器中运行 JavaScript 有两种方法。第一种方式,引用一些脚本来存放每个功能;此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈。第二种方式,使用一个包含所有项目代码的大型 .js 文件,但是这会导致作用域、文件大小、可读性和可维护性方面的问题。
为什么选择 webpack : webpack.docschina.org/concepts/wh…
webpack初次打包
webpack是一个模块打包工具,但是只能打包js,所以如果要打包其他的比如css,图片之类的模块,还需要借助loader,loader不能够解决的问题还需要借助插件plugin来拓展功能。
安装
yarn add webpack webpack-cli
配置
- 创建
webpack.config.js
文件 - 配置
webpack.config.js
const path = require('path')
module.exports={
entry: {
main:'./src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
}
}
复制代码
- 配置
packge.json
"script":{
"start":"webpack"
}
复制代码
解释配置
其实webpack里面已经默认配置了很多非常丰富的东西,比如output
,如果我们不配置output
的话,默认就会输出一个dist
文件夹,里面有着名叫bundle.js
的文件。
我们也可以自己配置:
- entry
入口文件的配置,如果只有一个入口文件,我们可以
entry:'./src/index.js'
。如果有多个入口文件,或者你想要给你的入口文件指定一个名字:里面的
main
就是入口文件的名字。这个入口文件的名字有一个好处就是,后面很多配置可能都会用到:我们不需要再手动输入
main
, 仅仅是使用'[name]'
就可以锁定到之前配置的main
。这样就可以实现,改一处,便可改所有。
-
output
path
是你输出的时候,会生成一个文件夹,文件夹里面有一个文件filename
,文件的名字叫做'[name].js'
。我们上面提到的,这个[name]
就对应着入口文件名!
loader
因为webpack只能够打包解析js代码,所以面对非js的模块,我们要用loader来解析!
css
安装
yarn add style-loader css-loader
配置
module.exports={
module: {
rules: [
{
test:/\.css$/,
loaders:['style-loader','css-loader']
},
]
}
}
复制代码
解释配置
test:/\.css$/
:当我们遇到以.css
结尾的文件,我们就走下面的loader。
css-loader
将css的文件集合在一起,然后由style-loader
将css代码转换成字符串插入到我们的输出文件main.js
里面。
scss
css已经不能满足我们了,我们要用功能更强大的scss!
安装
yarn add sass-loader style-loader css-loader node-sass
配置
module.exports={
module: {
rules: [
{
test:/\.scss$/,
loaders:['style-loader','css-loader','sass-loader']
},
]
}
}
复制代码
解释配置
sass-loader
先将scss代码编译成css代码,css-loader
将css的文件集合在一起,然后由style-loader
将css代码转换成字符串插入到我们的输出文件main.js
里面。
image
安装
yarn add file-loader url-loader
配置
module.exports={
module: {
rules: [
{
test:/\.(jpg|png|jpeg)$/,
use: {
loader: "url-loader",
options: {
outputPath:'images',
name:'[name].[ext]',
limit: 2048
}
}
]
}
}
复制代码
解释配置
我们遇到jpg png jpeg
结尾的,我们就走下面的配置!
如果我的图片大小是大于limit: 2048
2kb的,我就在dist
目录下创建一个images
的文件夹,文件夹里面放我用file-loader
打包的图片,名字是'[name].[ext]'
,[name]
就是我配置的入口文件名,.[ext]
我们的图片后缀。
如果我的图片大小是小于2kb的,我就用url-loader
,url-loader
会将图片转换成base64,插入在main.js
里面。
小图片的base64转换是没有意义的,因为小图片被base64转换了之后,大小反而变得更大了点。
plugin
plugin是用来拓展webpack的功能的!
html-webpack-plugin
安装
yarn add html-webpack-plugin
配置
const HtmlPlugin = require('html-webpack-plugin')
plugins:[
new HtmlPlugin({
template: "index.html"
})
]
复制代码
解释配置
template: "index.html"
用我们当前目录下的index.html
文件作为模版
在打包之后生成的dist
文件夹里面生产一个同样模版的index.html
文件。
clean-webpack-plugin
安装
yarn add clean-webpack-plugin
配置
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
plugins:[
new CleanWebpackPlugin({})
]
复制代码
解释配置
每次打包之前先删除dist
文件夹!
watch
在开发的时候,每次修改代码都要自己去重新打包,预览。
这样真的好麻烦!😓
我们期望,如果可以我一修改代码,代码就自己自动重新构建,然后页面马上也跟着刷新了!
提升工作效率,优化开发体验
安装
无
配置
"script":{
"start":"webpack --watch"
}
复制代码
解释配置
执行npm start
,运行index.html
,我们的文件就处于监听中!
当我们修改了代码之后,在浏览器,重新手动刷新一下页面,我们就可以看到最新的修改了!
devServer
每次修改了代码之后,还要手动重新刷新一下浏览器,太麻烦了!
而且,文件系统不能发ajax请求!这是一个让人头大的问题!
安装
yarn add webpack-dev-server
配置
devServer: {
contentBase:'./dist',
hot:true,
hotOnly:true,
open:true,
historyApiFallback: true,
overlay:true,
progress: true
}
复制代码
"script":{
"start":"webpack-dev-server"
}
复制代码
解释配置
webpack-dev-server
只能在开发环境下面用!
devServer
里面的配置也可以换一种方式写,写到script上面"start":"webpack-dev-server --hot --open"
当然,devServer
里面很多配置都是默认自带的:
contentBase:'./dist'
:在dist
目录下,开启服务器hot:true
开启热更新模式!当你修改了代码,你再也不用手动刷新页面了,浏览器会自动帮忙刷新!hotOnly:true
:即使HMR
不生效,浏览器也不自动刷新historyApiFallback: true
:如果我们的页面发生404了,就会去index.html
页面,而不是直接抛一个错误页面open:true
:当我们打包完成,自动打开浏览器,自动加载我们的index.html
页面overlay:true
:如果代码发生了错误,直接把错误情况显示在浏览器的页面上!progress: true
:显示你打包的进程
注意! 如果css代码已经从main.js
里面分离出来成为一个css文件了,那么css代码的热加载是不起作用的!
HMR
虽然我们有了一个特别好的 webpack-dev-server --hot
但是hot
功能,每次自动刷新浏览器的时候,都是加载全部的资源!就是相当于重新刷新了一次页面!
但是,我们希望,假如:如果我们只修改了css文件,那么就重新加载css文件好了!
只替换我们更新的模块!
Hot Module Replacement = HMR
安装
无
配置
const webpack = require('webpack')
plugins: [
new webpack.HotModuleReplacementPlugin()
]
复制代码
在我们的index.js入口文件里面再塞一个:
if (module.hot) {
module.hot.accept();
}
复制代码
解释配置
HotModuleReplacementPlugin
可以实现热模块的更新,当我们更新了代码的时候,浏览器network加载我们生成的hot.update的js和json文件。而不是之前的所有资源都再重新加载一次!- 我们必须在某个文件接受
module.hot.accept()
,如果没有文件接受,就不会生成热模块替换的文件。 - 为什么我们的css不需要写
module.hot.accept()
,是因为css-loader
已经为我们完成了这一项操作 - 我们可以在
module.hot
监听是哪个文件发生了修改,再做自己想做的操作:
if (module.hot) {
console.log('修改了...')
module.hot.accept('@components/child', () => {
ReactDom.render( <App/>,document.getElementById('root'))
});
}
复制代码
比如我监听到了`'@components/child'`这个文件发生了修改,那么我就重新render一下页面!
jsx
安装
yarn add babel-loader
配置
{
test:/\.(js|jsx)$/,
exclude:/node_modules/,
loader: 'babel-loader'
}
复制代码
解释配置
排除node_modules
文件夹下的,以js
和jsx
结尾的文件,我们要用babel-loader
将es6的代码转换成es5的!
tsx
安装
yarn add awesome-typescript-loader
配置
{
test:/\.(ts)x$/,
exclude:/node_modules/,
loader: "awesome-typescript-loader"
}
复制代码
解释配置
排除node_modules
文件夹下的,以ts
和tsx
结尾的文件,我们要用awesome-typescript-loader
将ts代码转换成可以编译的js代码
react
安装
yarn add react react-dom @babel/preset-env @babel/preset-react
配置
创建.babelrc文件
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
复制代码
解释配置
配置好了就可以用react了….
resolve为代码的引入带来的方便
安装
无
配置
resolve: {
extensions: ['.js','.jsx','.tsx'],
mainFiles: ['index'],
alias: {
'@components':path.resolve(__dirname, 'src/components'),
'@pages': path.resolve(__dirname, 'src/pages'),
'@assets': path.resolve(__dirname, 'src/assets')
}
}
复制代码
解释配置
extensions: ['.js','.jsx','.tsx']
: 以js,jsx,tsx文件结尾的,我们在import的时候,可以不用写后缀!mainFiles: ['index']
:如果这个文件叫做index
,那么我们可以不用写文件名,直接import上一级的文件夹名就可以了alias
:我们做import引入的时候,如果我们改变了文件的路径,那么引入的路径也要改,路径改很麻烦,所以我们使用alias
。如果引入路径为src/components
,我们可以直接用@components
代替!
动态chunk
安装
配置
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js",
chunkFilename: "[name].chunk.js"
}
复制代码
解释配置
chunkFilename: "[name].chunk.js"
当你遇到动态引入的模块的时候,这个chunkFilename
就会起作用!
如何动态引入?
两种动态引入的方式,一种自己写的,一种react自带的。
- 自己写的
const getAsyncComponent =(load)=>{
return class AsyncComponent extends React.Component{
componentDidMount() {
load().then(({default: Component})=>{
this.setState({
Component
})
})
}
render() {
const {Component} = this.state || {}
return Component ? <Component {...this.props}/> : <div>loading...</div>
}
}
}
const asyncUser = getAsyncComponent(()=>import(/* webpackChunkName:'page-user'*/'@pages/user'))
复制代码
- react自带的 Suspense lazy
lazy(()=>import(/* webpackChunkName:'page-user'*/'@pages/user'))
复制代码
- 全部代码
import React, { Suspense, Component, lazy } from 'react'
import ReactDom from 'react-dom'
import './index.scss'
import { Route, BrowserRouter, Switch } from 'react-router-dom'
import Home from '@pages/home';
// import {User} from "@pages/user";
// import {About} from "@pages/about";
// 如果不注销这个同步的import,那么chunk就不能动态生成...
// const asyncUserComponent = ()=>import(/* webpackChunkName: 'page-user' */'@pages/user').then(({default: component})=> component())
const getAsyncComponent =(load)=>{
return class AsyncComponent extends React.Component{
componentDidMount() {
load().then(({default: Component})=>{
this.setState({
Component
})
})
}
render() {
const {Component} = this.state || {}
return Component ? <Component {...this.props}/> : <div>loading...</div>
}
}
}
const asyncUser = getAsyncComponent(()=>import(/* webpackChunkName:'page-user'*/'@pages/user'))
const asyncAbout = getAsyncComponent(()=>import(/* webpackChunkName:'page-about'*/'@pages/about'))
class App extends React.Component{
render(){
return (
<Suspense fallback={<div>loading...</div>}>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/user' component={lazy(()=>import(/* webpackChunkName:'page-user'*/'@pages/user'))}/>
<Route path='/about' component={asyncAbout}/>
</Switch>
</BrowserRouter>
</Suspense>
)
}
}
ReactDom.render(<App/>,document.getElementById('root'))
复制代码
- 解释
()=>import(/* webpackChunkName:'page-user'*/'@pages/user')
复制代码
`webpackChunkName`就是chunk的名称,最后这个chunk会生成一个文件,文件名叫做`page-user.chunk.js`
静态chunk
静态chunk就是传统import方式引入的chunk
比如:import React from 'react'
安装
无
配置
optimization: {
usedExports: true,
splitChunks: {
chunks: "all",
cacheGroups: {
vendors:{
test:/node_modules/,
priority:-10,
},
ui:{
test:/src\/components/,
minSize:0,
reuseExistingChunk: true,
priority:-20
}
}
}
}
复制代码
解释配置
如果这个import的模块是属于node_modules
目录下的,就塞到vendors
模块下,打包出来的文件名就叫做:vendors~main.chunk.js
如果这个import的模块是属于src/components
目录下的,就塞到ui
模块下,打包出来的文件名就叫做:ui~main.chunk.js
压缩js
这个功能一般是用到生产模式下的
安装
yarn add terser-webpack-plugin
配置
const TerserJSPlugin = require("terser-webpack-plugin");
optimization:{
minimizer: [
new TerserJSPlugin({})
]
},
复制代码
解释配置
压缩js代码
css分离文件
安装
yarn add mini-css-extract-plugin
配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module: {
rules: [
{
test:/\.scss$/,
loaders:[MiniCssExtractPlugin.loader,'css-loader','sass-loader']
},
{
test:/\.css$/,
loaders:[MiniCssExtractPlugin.loader,'css-loader']
},
]
}
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
],
复制代码
解释配置
把css代码从main.js文件里面剥离出一个单独的css文件。
压缩css
一般用在生产模式下。
安装
yarn add optimize-css-assets-webpack-plugin
配置
optimization:{
minimizer: [
new OptimizeCSSAssetsPlugin({})
]
}
复制代码
解释配置
css代码被压缩了
DllPlugin
我们只希望第三方的模块在第一次打包的时候分析,以后都不分析了。
加快打包的速度!
安装
yarm add add-asset-html-webpack-plugin
配置
- 创建
webpack.dll.js
const {DllPlugin} = require('webpack')
const path = require('path')
module.exports={
mode:'production',
entry:{
react:['react','react-dom'],
time:['timeago.js']
},
output:{
filename: "[name].dll.js",
path: path.resolve(__dirname, 'dll'),
library: '[name]'
},
plugins:[
new DllPlugin({
name:'[name]',
path: path.resolve(__dirname, './dll/[name].manifest.json')
})
]
}
复制代码
"dll": "webpack --config webpack.dll.js"
-
配置
webpack.config.js
:
const fs = require('fs')
const {DllReferencePlugin} = require('webpack')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const dllPlugins = ()=>{
const plugins = []
const files = fs.readdirSync(path.resolve(__dirname, './dll'))
files.forEach(file => {
if (/.*\.dll.js/.test(file)){
plugins.push(new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, './dll',file)
}))
}
if (/.*\.manifest.json/.test(file)){
plugins.push(new DllReferencePlugin({
manifest:path.resolve(__dirname, './dll', file)
}))
}
})
return plugins;
}
plugins: [
...dllPlugins()
]
复制代码
解释配置
1.注意
先运行yarn run dll
,这样先解析webpack.dll.js
,生成了dll文件夹以及关于dll的文件。
再执行yarn start
,这样在运行webpack.config.js
的时候,执行到fs.readdirSync(path.resolve(__dirname, './dll'))
这一步才不会因为找不到文件夹文件而出错!
2.DllReferencePlugin :
意思是当我们打包的时候,我们发现第三方的模块,我们之前会从node_modules
里面一遍一遍的找
现在我们会先从dll/vendors.manifest.json
里面找映射关系
如果第三方模块在映射关系里,我们知道,这个第三方模块,就在vendors.dll.js
里面,
那么就会从全局变量里面拿, 因为第三方模块第一次打包的时候,就生成里全局变量了
就不用再在node_modules
里面一点一点分析,一点一点找了,加快了打包的速度
3.AddAssetHtmlPlugin:
最后把我么打包生成的*.dll.js
文件,作为静态文件插入到我们的index.html
里面
分离环境
开发环境跟到生产环境是不一样的,有些东西开发环境能用,生产环境不见得能用,比如devServer
但是有些代码又是公用的,比如css-loader
。
开发环境跟到生产环境注重的东西也不一样。开发环境更注重写代码的效率,方便。生产环境更注重包的大小,轻便。
所以,要针对不同的环境做不同的配置!
安装
yarn add webpack-merge
配置
比如生产环境:(开发环境类似啦)
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base')
const prodConfig = {...}
module.exports=merge(baseConfig, prodConfig)
复制代码
解释配置
在不同的环境,根据不同的侧重,做不同的事!
最后
继续学习
作者:西门吹喵
链接:https://juejin.im/post/5e69e1f3f265da57663fe9f2
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com