webpack由淺入深——(webpack基礎配置)

webpack的做用

構建就是把源代碼轉換成發佈到線上的可執行JavaScrip、CSS、HTML代碼,包括以下內容:css

  • 代碼校驗:在代碼被提交到倉庫前須要校驗代碼是否符合規範,以及單元測試是否經過。
  • 代碼轉換:TypeScript 編譯成 JavaScript、SCSS 編譯成 CSS 等。
  • 模塊合併:在採用模塊化的項目裏會有不少個模塊和文件,須要構建功能把模塊分類合併成一個文件。
  • 代碼分割:提取多個頁面的公共代碼、提取首屏不須要執行部分的代碼讓其異步加載。
  • 文件優化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合並圖片等。
  • 自動刷新:監聽本地源代碼的變化,自動從新構建、刷新瀏覽器。
  • 自動發佈:更新完代碼後,自動構建出線上發佈代碼並傳輸給發佈系統。

webapck核心概念

  • Entry:入口,Webpack 執行構建的第一步將從 Entry 開始,可抽象成輸入。
  • Output:輸出結果,在Webpack通過一系列處理並得出最終想要的代碼後輸出結果。
  • Module:模塊,在 Webpack裏一切皆模塊,一個模塊對應着一個文件。Webpack會從配置的 Entry 開始遞歸找出全部依賴的模塊。
  • Chunk:代碼塊,一個 Chunk 由多個模塊組合而成,用於代碼合併與分割。
  • Loader:模塊轉換器,用於把模塊原內容按照需求轉換成新內容。
  • Plugin:擴展插件,在Webpack構建流程中的特定時機注入擴展邏輯來改變構建結果或作你想要的事情。

webpack工做過程

  1. Webpack 啓動後會從Entry裏配置的Module開始遞歸解析 Entry 依賴的全部 Module。
  2. 每找到一個 Module, 就會根據配置的Loader去找出對應的轉換規則,對Module 進行轉換後,再解析出當前 Module 依賴的 Module。 這些模塊會以 Entry 爲單位進行分組,一個 Entry 和其全部依賴的 Module 被分到一個組也就是一個 Chunk。
  3. 最後 Webpack 會把全部 Chunk 轉換成文件輸出。 在整個流程中 Webpack 會在恰當的時機執行Plugin 裏定義的邏輯。

webpack系列文章

  1. webpack由淺入深——(webpack基礎配置)
  2. webpack由淺入深——(webpack優化配置)
  3. webpack由淺入深——(tapable)
  4. webpack由淺入深——(webapck簡易版)
  5. webpack由淺入深——(ast、loader和plugin)

初始化項目

首先確保電腦已經安裝了nodejs,推薦採用nvm的形式進行安裝,這樣就不用配置環境變量或者建立軟連接。html

mkdir webpack-learn //經過命令行建立文件夾
cd webpack-learn    //打開建立的文件夾
npm init -y //初始化一個項目
npm install webpack webpack-cli -D //本地安裝webpack和webpack-cli
mkdir src   //建立src目錄來存放源代碼
mkdir dist  //建立dist目錄來存放打包後的代碼
複製代碼

在src目錄下建立index.js前端

let str = require('./a');
console.log(str);
複製代碼

在src目錄下建立a.jsnode

module.exports='webpack-learn'
複製代碼

在dist目錄下建立index.html文件react

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
複製代碼

利用webpack進行打包,關於commonjs不清楚的,請參考require()源碼解讀jquery

npx webpack -- mode development //以默認以開發模式進行打包
複製代碼

mode-d

Entry(入口)和Output(輸出)

從上面的代碼中能夠看到文件打包到了dist/main.js中,因此須要進行相應的配置webpack.config.js來自定義結果文件名。webpack

單入口

  • 字符串形式
const path=require('path');
module.exports={
    entry: './src/index.js',    //入口
    output: {
        path: path.resolve(__dirname,'dist'),   //出口,絕對路徑
        filename:'bundle.js'
    },
    module: {},     //配置loader
    plugins: [],    //配置插件
    devServer: {}   //配置本地服務
    resolve:{},     //配置解析文件路徑等
}
複製代碼
  • 數組形式
//index.js
console.log('hello');
複製代碼
//a.js
console.log('world')
複製代碼
const path=require('path');
module.exports={
    //index和a之間沒有依賴關係,只是單純的合併
    entry: ['./src/index.js','./src/a.js'],
    output: {
        path: path.resolve(__dirname,'dist'),  
        filename:'bundle.js'
    },
    module: {},     
    plugins: [],    
    devServer: {}   
    resolve:{},
}
複製代碼

多入口

//a.js
let str =require('./c')
console.log(str);
複製代碼
//b.js
let str =require('./d')
console.log(str);
複製代碼
//c.js
module.export = 'hello'
複製代碼
//d.js
module.export = 'world'
複製代碼
const path=require('path');
module.exports={
    //多入口拆分功能,能夠兩個頁面分別引用一個,也能夠一個頁面引用多個
    //配合後面的html-webpack-plugin使用
    entry: {
        pageA:'./src/a',
        pageB:'./src/b'
    },
    output: {
        path: path.resolve(__dirname,'dist'), 
        //帶有哈希值的文件名exp:pageA.fa112c6二、pageB.fa112c62
        filename:'[name].[hash:8].js'    
    },
    module: {},     
    plugins: [],    
    devServer: {}   
    resolve:{},
}
複製代碼

自動生成頁面

如今頁面是手動建立到dist目錄下的,一個頁面還好,若是存在多個頁面,手動創造html的代價是很大的,能夠利用html-webpack-plugin來自動建立頁面:web

npm install html-webpack-plugin -D
複製代碼
//src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
</body>
</html>
複製代碼
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry:{
        pageA:'./src/a',
        pageB:'./src/b'
    },
    output: {
        path: path.resolve(__dirname,'dist'), 
        filename:'[name].[hash:8].js'    
    },
    module: {},
    plugins: [
        //在實際項目中,經過讀取須要建立的頁面信息,遍歷建立實例
         new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'pageA.html',
            chunks:['pageA,pageB'],   //數組,能夠放多個chunk
            //頁面資源攜帶哈希值exp:pageA.fa112c62?r2452567&4124
            //中間哈希一直都有,這個後面的哈希只在頁面引用添加在頁面中
            hash:true,  
            minify:{
                collapseWhitespace:true, //壓縮代碼,去除空格和換行
                removeAttributeQuotes:true//壓縮代碼,去除屬性雙引號    
            }
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'pageB.html',
            chunks:['pageB'],
            hash:true,
            minify:{
                collapseWhitespace:true,
                removeAttributeQuotes:true
            }
        })
    ],
    devServer: {},
    resolve:{},
}
複製代碼

配置script

因爲每次打包都須要執行npx webpack --mode development,因此能夠在package.json中進行配置:express

"scripts": {
    //等價於webpack --config webpack.config.js --mode development
    //默認是執行webpack.config.js,能夠根據env來配置執行不一樣的文件
    "start": "webpack --mode development"
  },
複製代碼

devServer(本地服務器)

首先必須的安裝插件webpack-dev-derver:npm

npm install webpack-dev-server -D   
複製代碼

修改script:

"scripts": {
    "dev":"webpack-dev-server --mode development"
    "build": "webpack --mode development"
 },
複製代碼

修改配置文件:

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id='app'></div>
</body>
</html>
複製代碼
//a.js
module.exports = 'hello'
複製代碼
//index.js
let str = require('./a');
document.getElementById('app').innerHTML = str;
複製代碼
const path=require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports={
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'), 
        filename:'bundle.js'
    },
    module: {}, 
    plugins: [
         new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            hash:true,
            minify:{
                collapseWhitespace:true,
                removeAttributeQuotes:true
            }
        })
    ],
    devServer: {
        //devServer會在打包的時候把生成的文件放在內存中,
        //而且是以根目錄爲參照目錄而不是dist目錄,因此須要修改
        contentBase:'./dist',
        port:'3000',
    } 
    resolve:{},
}
複製代碼

經過瀏覽器,輸入localhost:3000就能夠看到

本地服務
經過修改a.js中module.exports = 'hello1',能夠看到
熱更新
這樣會有一個問題,就是隻要源代碼中存在改動,就會刷新頁面。假如在react本地開發的時候,有不少組件,其中一個組件的代碼修改了,不但願全部的組件都更新,能夠利用熱更新來解決:

const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports={
    entry:'./src/index.js',
    output: {
        filename:'bundle.js',
        path: path.resolve(__dirname,'dist')
    },
    module: {},
    plugins: [
        //使用熱更新插件
        new webpack.HotModuleReplacementPlugin();
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            hash:true,
            minify:{
                collapseWhitespace:true,
                removeAttributeQuotes:true
            }
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true    //熱更新開關,使用websocket來通知頁面更新
    },
    resolve:{},
}
複製代碼
//index.js
let str = require('./a');
document.getElementById('app').innerHTML = str;

//這裏必須加這段,否則的話,仍是沒有辦法使用熱更新
if(module.hot){
    module.hot.accept();
}
複製代碼

配置proxy

devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true,
        proxy:{
            target:'http://xxxxx'   //代理的服務器
            pathRewirte:{
                '/xxx':'/yyy'
            }
        }
    },
複製代碼
  • webpack服務集成到本身的本地服務
// 把webpack-dev-server的配置引入到本身的本地服務
const express = require('express');
const webpackDevMiddleware = require('webpack-dev-middleware');
// 引入webpack配置文件
const config = require('./webpack.config');
let app = express();

cosnt webpack = require('webpack');
let compiler = webpack(config);     //用webpack進行編譯
app.use(webpackDevMiddleware(compiler)); 

app.listen(3000);
複製代碼

module中的loader

webpack每找到一個Module,就會根據配置的Loader去找出對應的轉換規則,讓js能過編譯css、ejs、jsx和圖片的各類格式。

css相關loader

  • style-loader 和css-loader
//index.css
body{
    background:red;
    border-radio:4px;
}
複製代碼
//通常前端代碼使用import引入模塊,node服務使用require引入模塊
import str from './a'
import './index.css'
document.getElementById('app').innerHTML=str

if(module.hot){
    module.hot.accept();
}
複製代碼

執行npm run build以後發現出現如下報錯,說明須要配置loader:

loader報錯
首先須要安裝loader

npm install css-loader style-loader -D
複製代碼
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                //多個loaders用數組,loader的形式有字符串和對象兩種
                //字符串形式:'xxx?option1!yyyy'
                //對象形式{loader:'xxx',options:{option1:yyyy}}
                //less-laoder將less轉化爲css,css-loader解析css,style-loader插入到style標籤中
                use:['style-oader','css-loader']    
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
複製代碼

用npm run dev啓動服務能夠看到效果,可是這樣有一個問題,css樣式採用的內嵌式,最好能抽離出來使用外鏈式引入,能夠使用插件mini-css-extract-plugin:

npm install mini-css-extract-plugin -D
複製代碼
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                    options:{
                        //將css中的路經前面添加,background:url('xxxx')
                        //http://ssss/xxxxx
                        publicPath:'http://sssss'    
                    }
                },
                'css-loader'
                ]    
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
複製代碼
  • postcss-loader和autoprefixer插件

因爲前端寫的代碼要兼容各類瀏覽器,css屬性爲了兼容各大瀏覽器,每每須要添加各類前綴,可是同一個屬性寫多份,這個工做量仍是比較大的,有一個postcss-loader能夠配合autoprefixer插件使用

//index.css
body{
    background:red;
    transform: rotate(0,0,100deg);
}
複製代碼
npm install postcss-loader autoprefixer -D
複製代碼
//建立postcss.config.js
module.exports ={
    plugins:[
        require('autoprefixer')
    ]
} 
複製代碼
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[
                {loader:MiniCssExtractPlugin.loader},
                'css-loader',
                'postcss-loader'    //添加css前綴處理laoder
                ]    
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
複製代碼

圖片相關的loader

項目中引入圖片的方式有三種:

  1. 經過js引入
//index.js
import './index.css'
import jpg from './1.jpg'
let img = new Image();
img.src=jpg;
document.body.appendChild(img);
if(module.hot){
    module.hot.accept();
}
複製代碼
  1. 經過css引入
//index.css
body{
    background: url('./1.jpg') no-repeat right;
}
複製代碼
  1. 經過img的src屬性引入
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <img src="./1.jpg" alt="">
</body>
</html>
複製代碼

處理前兩種引入圖片的方式須要使用file-loader和url-loader,其中url-laoder內部會引用file-loader,它們的做用就是解析js和css中的圖片連接而後將圖片變成base64。後一種引入圖片的方式須要使用html-withimg-loader。

npm install file-loader url-loader html-withimg-loader -D
複製代碼
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        //大於8k的圖片會打包到dist目錄下,小於8k的圖片會生成base64插入到引用的地方
                        //base64會使資源變大1/4,可是base64無需發送請求,資源比較小時使用base64最佳
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
複製代碼

img-loader

js相關的loader

因爲ES6的簡潔和API擴展,有不少開發者使用ES6進行開發,可是因爲瀏覽器的品牌和版本的不一樣,就出現了開發時使用ES6而後贊成轉化成ES5的狀況。這時候就須要使用babel對ES6+的語法進行轉譯,關於babel的各類配置能夠參考對babel-transform-runtime,babel-polyfill的一些理解

npm install babel-core babel-loader babel-preset-env babel-preset-stage-0 babel-plugin-tranform-runtime -D
複製代碼
//建立.babelrc文件
//preset中包含了一組用來轉換ES6+的語法的插件,可是還不轉換新的API
//如需使用新的API,例如set(),還須要使用對應的轉換插件或者polyfill(填充庫)
{
    presets:{
        'env',      //環境變量,根據不一樣瀏覽器環境而對應的轉碼
        'stage-0'   //轉譯ES6+(0 > 1 > 2 > 3 > 4)
    }
    plugins:{
        'tranform-runtime'
    }
}
複製代碼
const path = require('path');
const webpack =require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            },
            {   //使用babel-loader
                test:/\.js$/,
                use:'babel-loader',
                exclude:/node_modules/  //排除編譯ndoe——modules   
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
複製代碼

Plugin(插件)

plugin和loader的區別在於loader只在編譯module時執行,而plugin可能在webapck工做流程的各個階段執行。

clean-webpack-plugin

清除dist目錄

module.exports = {
    mode:'production',  生產環境會自動壓縮   
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            },
            {   
                test:/\.js$/,
                use:'babel-loader',
                exclude:'/node_modules/'
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist/*.*']),   //清空dist目錄
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    externals:{
        'jquery':'$'
    }
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
複製代碼

uglifyjs-webpack-plugin和optimize-css-assets-webpack-plugin

module.exports = {
    mode:'production',  生產環境會自動壓縮   
    entry:{
        index:'./src/index'
    },
    output: {
        filename:'[name].[hash:8].js',
        path: path.resolve(__dirname,'dist')
    },
    optimization:{
        minimizer:{
            new UglifyJSPlugin({    //壓縮js
                cache:true,
                parallel:ture,  //並行壓縮
                sourthMap:true, //啓動sourthMap
            }) ,
            new OptimizeCssAssetsPlugin()   //壓縮css
        }
    },
    module: {
        rules:[
            {
                test:/\.css$/,
                use:[{
                    loader:MiniCssExtractPlugin.loader,
                },'css-loader']
            },
            {
                test:/\.jpg|png/,
                use:{
                    loader:'url-loader',
                    options:{
                        limit:8*1024   
                    }
                }
            },
            {
                test:/\.html$/,
                use:'html-withimg-loader'
            },
            {   
                test:/\.js$/,
                use:'babel-loader',
                exclude:'/node_modules/'
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new MiniCssExtractPlugin({
            filename:index.css,
        }),
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            chunks:['index']
        })
    ],
    externals:{
        'jquery':'$'
    }
    devServer: {    
        contentBase:'./dist',
        port:'3000',
        hot:true
    },
    resolve:{},
}
複製代碼

經常使用插件

  • html-webpack-plugin: 建立html並插入script標籤
  • autoprefixer: 給cssh加前綴
  • mini-css-extract-plugin: 抽離css樣式link到html
  • webpack-dev-server: 啓動webpack服務
  • webpack-dev-middleware:webpack服務集成到本地的服務
  • uglifyjs-webpack-plugin:壓縮js
  • optimize-css-assets-webpack-plugin:壓縮css
  • clean-webpack-plugin:清空目錄

結語

學習webpack的過程是從官方文檔開始學習的,照着敲了一遍,而後搜索相關的內容。

相關文章
相關標籤/搜索