被女朋友手把手入門webpack

爲何要構建?

前端發展好快的。css

愈來愈多的思想和框架都出來了。html

就是爲了提升開發的效率。前端

好比ES6須要經過babel轉成ES5才能在瀏覽器上面運行吧。node

好比SCSS須要轉換成css才能在瀏覽器運行吧react

...webpack

這些思想和框架,都是有構建需求的。es6

爲何要選擇webpack來作構建?

webpack 把一切都當成模塊!web

webpack能夠經過plugin來拓展功能!ajax

webpack的社區很是的龐大活躍,緊跟着時代!生態鏈完整,維護性也很高!typescript

想要理解爲何要使用 webpack,咱們先回顧下歷史,在打包工具出現以前,咱們是如何在 web 中使用 JavaScript 的。 在瀏覽器中運行 JavaScript 有兩種方法。第一種方式,引用一些腳原本存放每一個功能;此解決方案很難擴展,由於加載太多腳本會致使網絡瓶頸。第二種方式,使用一個包含全部項目代碼的大型 .js 文件,可是這會致使做用域、文件大小、可讀性和可維護性方面的問題。

爲何選擇 webpack : webpack.docschina.org/concepts/wh…

webpack初次打包

webpack是一個模塊打包工具,可是隻能打包js,因此若是要打包其餘的好比css,圖片之類的模塊,還須要藉助loader,loader不可以解決的問題還須要藉助插件plugin來拓展功能。

安裝

yarn add webpack webpack-cli

配置

  1. 建立webpack.config.js文件
  2. 配置webpack.config.js
    const path = require('path')
    
    module.exports={
        entry: {
            main:'./src/index.js'
        },
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: "[name].js"
        }
    }
    複製代碼
  3. 配置packge.json
    "script":{
        "start":"webpack"
    }
    複製代碼

解釋配置

其實webpack裏面已經默認配置了不少很是豐富的東西,好比output,若是咱們不配置output的話,默認就會輸出一個dist文件夾,裏面有着名叫bundle.js的文件。

咱們也能夠本身配置:

  1. entry

    入口文件的配置,若是隻有一個入口文件,咱們能夠entry:'./src/index.js'

    若是有多個入口文件,或者你想要給你的入口文件指定一個名字:裏面的main就是入口文件的名字。

    這個入口文件的名字有一個好處就是,後面不少配置可能都會用到:咱們不須要再手動輸入main, 僅僅是使用'[name]'就能夠鎖定到以前配置的main

    這樣就能夠實現,改一處,即可改全部。

  2. 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裏面不少配置都是默認自帶的:

  1. contentBase:'./dist':在dist目錄下,開啓服務器
  2. hot:true開啓熱更新模式!當你修改了代碼,你不再用手動刷新頁面了,瀏覽器會自動幫忙刷新!
  3. hotOnly:true:即便HMR不生效,瀏覽器也不自動刷新
  4. historyApiFallback: true:若是咱們的頁面發生404了,就會去index.html頁面,而不是直接拋一個錯誤頁面
  5. open:true:當咱們打包完成,自動打開瀏覽器,自動加載咱們的index.html頁面
  6. overlay:true:若是代碼發生了錯誤,直接把錯誤狀況顯示在瀏覽器的頁面上!
  7. 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();
}
複製代碼

解釋配置

  1. HotModuleReplacementPlugin能夠實現熱模塊的更新,當咱們更新了代碼的時候,瀏覽器network加載咱們生成的hot.update的js和json文件。而不是以前的全部資源都再從新加載一次!
  2. 咱們必須在某個文件接受module.hot.accept(),若是沒有文件接受,就不會生成熱模塊替換的文件。
  3. 爲何咱們的css不須要寫module.hot.accept(),是由於css-loader已經爲咱們完成了這一項操做
  4. 咱們能夠在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文件夾下的,以jsjsx結尾的文件,咱們要用babel-loader將es6的代碼轉換成es5的!

tsx

安裝

yarn add awesome-typescript-loader

配置

{
    test:/\.(ts)x$/,
    exclude:/node_modules/,
    loader: "awesome-typescript-loader"
}
複製代碼

解釋配置

排除node_modules文件夾下的,以tstsx結尾的文件,咱們要用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')
        }
    }
複製代碼

解釋配置

  1. extensions: ['.js','.jsx','.tsx']: 以js,jsx,tsx文件結尾的,咱們在import的時候,能夠不用寫後綴!
  2. mainFiles: ['index']:若是這個文件叫作index,那麼咱們能夠不用寫文件名,直接import上一級的文件夾名就能夠了
  3. 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自帶的。

  1. 本身寫的
    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'))
    複製代碼
  2. react自帶的 Suspense lazy
    lazy(()=>import(/* webpackChunkName:'page-user'*/'@pages/user'))
    複製代碼
  3. 所有代碼
    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'))
    
    複製代碼
  4. 解釋
    ()=>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

配置

  1. 建立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')
        })
    ]
}

複製代碼
  1. "dll": "webpack --config webpack.dll.js"

  2. 配置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)
複製代碼

解釋配置

在不一樣的環境,根據不一樣的側重,作不一樣的事!

最後

繼續學習

相關文章
相關標籤/搜索