前端發展好快的。css
愈來愈多的思想和框架都出來了。html
就是爲了提升開發的效率。前端
好比ES6須要經過babel轉成ES5才能在瀏覽器上面運行吧。node
好比SCSS須要轉換成css才能在瀏覽器運行吧react
...webpack
這些思想和框架,都是有構建需求的。es6
webpack 把一切都當成模塊!web
webpack能夠經過plugin來拓展功能!ajax
webpack的社區很是的龐大活躍,緊跟着時代!生態鏈完整,維護性也很高!typescript
想要理解爲何要使用 webpack,咱們先回顧下歷史,在打包工具出現以前,咱們是如何在 web 中使用 JavaScript 的。 在瀏覽器中運行 JavaScript 有兩種方法。第一種方式,引用一些腳原本存放每一個功能;此解決方案很難擴展,由於加載太多腳本會致使網絡瓶頸。第二種方式,使用一個包含全部項目代碼的大型 .js 文件,可是這會致使做用域、文件大小、可讀性和可維護性方面的問題。
爲何選擇 webpack : webpack.docschina.org/concepts/wh…
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]
就對應着入口文件名!
由於webpack只可以打包解析js代碼,因此面對非js的模塊,咱們要用loader來解析!
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
裏面。
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
裏面。
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是用來拓展webpack的功能的!
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
文件。
yarn add clean-webpack-plugin
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
plugins:[
new CleanWebpackPlugin({})
]
複製代碼
每次打包以前先刪除dist
文件夾!
在開發的時候,每次修改代碼都要本身去從新打包,預覽。
這樣真的好麻煩!😓
咱們指望,若是能夠我一修改代碼,代碼就本身自動從新構建,而後頁面立刻也跟着刷新了!
提高工做效率,優化開發體驗
無
"script":{
"start":"webpack --watch"
}
複製代碼
執行npm start
,運行index.html
,咱們的文件就處於監聽中!
當咱們修改了代碼以後,在瀏覽器,從新手動刷新一下頁面,咱們就能夠看到最新的修改了!
每次修改了代碼以後,還要手動從新刷新一下瀏覽器,太麻煩了!
並且,文件系統不能發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代碼的熱加載是不起做用的!
雖然咱們有了一個特別好的 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()
,若是沒有文件接受,就不會生成熱模塊替換的文件。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一下頁面!yarn add babel-loader
{
test:/\.(js|jsx)$/,
exclude:/node_modules/,
loader: 'babel-loader'
}
複製代碼
排除node_modules
文件夾下的,以js
和jsx
結尾的文件,咱們要用babel-loader
將es6的代碼轉換成es5的!
yarn add awesome-typescript-loader
{
test:/\.(ts)x$/,
exclude:/node_modules/,
loader: "awesome-typescript-loader"
}
複製代碼
排除node_modules
文件夾下的,以ts
和tsx
結尾的文件,咱們要用awesome-typescript-loader
將ts代碼轉換成能夠編譯的js代碼
yarn add react react-dom @babel/preset-env @babel/preset-react
建立.babelrc文件
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
複製代碼
配置好了就能夠用react了....
無
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
代替!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'))
複製代碼
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就是傳統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
這個功能通常是用到生產模式下的
yarn add terser-webpack-plugin
const TerserJSPlugin = require("terser-webpack-plugin");
optimization:{
minimizer: [
new TerserJSPlugin({})
]
},
複製代碼
壓縮js代碼
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文件。
通常用在生產模式下。
yarn add optimize-css-assets-webpack-plugin
optimization:{
minimizer: [
new OptimizeCSSAssetsPlugin({})
]
}
複製代碼
css代碼被壓縮了
咱們只但願第三方的模塊在第一次打包的時候分析,之後都不分析了。
加快打包的速度!
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)
複製代碼
在不一樣的環境,根據不一樣的側重,作不一樣的事!
繼續學習