1. 程式人生 > >基於webpack4.X從零搭建React腳手架

基於webpack4.X從零搭建React腳手架


專案初始化

$ npm init
複製程式碼

安裝webpack

  • 本次建立是基於webpack4
$ npm install --save-dev
複製程式碼

新建webpack配置檔案

  • 在根目錄建立build資料夾,新增一個js檔案,命名為webpack.base.conf.js
// webpack.base.conf.js 檔案
const path = require('path');
const DIST_PATH = path.resolve(__dirname, '../dist');
module.exports = {
        entry: {
            app: './app/index.js'
}, output: { filename: "js/bundle.js", path: DIST_PATH } }; 複製程式碼

使用merge的方式來組織webpack基礎配置和不同環境的配置

  • 先安裝webpack-merge:
$ npm install --save-dev webpack-merge
複製程式碼
  • 在build資料夾中再新增一個js檔案,命名為 webpack.prod.conf.js
// webpack.prod.conf.js 檔案
const merge = require('webpack-merge'
); const baseWebpackConfig = require('./webpack.base.conf'); module.exports = merge(baseWebpackConfig, { mode: 'production' }); 複製程式碼

在根目錄下建立app目錄,然後建立index.js檔案

var element =document.getElementById('root');
element.innerHTML = 'hello, world!';
複製程式碼
  • 在根目錄建立一個public資料夾,然後新建一個index.html檔案
// index.html
<!DOCTYPE html>
<html lang="en"
> <head> <meta charset="UTF-8"> <title>從零開始搭建react工程</title> </head> <body> <div id="root"></div> <script src="../dist/js/bundle.js"></script> </body> </html> 複製程式碼

當前專案目錄樹

  |- /app
    |- index.js
  |- /node_modules
  |- /public
    |- index.html
  |- /build
    |- webpack.base.conf.js
    |- webpack.prod.conf.js
  |- package.json
  |- package-lock.json
複製程式碼

安裝webpack-cli

  • webpack 4.0 版本之後的webpack,已經將webpack命令工具遷移到webpack-cli模組了,需要安裝 webpack-cli
$ npm install --save-dev webpack-cli
複製程式碼

package.json檔案 scripts屬性配置一個build命令

  • 其值為:webpack --config build/webpack.prod.conf.js,以下是scripts的相關程式碼
// package.json
"scripts": {
    "build": "webpack --config build/webpack.prod.conf.js",
    "test": "echo \"Error: no test specified\" && exit 1"
},
複製程式碼

安裝React

$ npm install --save react react-dom
複製程式碼
  • 修改app目錄下的index.js的程式碼
import React from "react";
import ReactDom from "react-dom";

ReactDom.render(
    <h1>hello, world!</h1>,
    document.getElementById("root")
);
複製程式碼
  • 注意 import 屬於ES6規範,因此需要轉譯ES2015+的語法,安裝並配置 babel 以及相關依賴
$ npm install --save-dev babel-loader babel-core babel-preset-env babel-preset-react
複製程式碼
  • 根目錄建立.babelrc檔案,配置presets.
{
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": [
            "> 1%",
            "last 5 versions",
            "ie >= 8"
          ]
        }
      }
    ],
    "react"
  ]
}
複製程式碼
  • 修改webpack.base.conf.js檔案
// webpack.base.conf.js
const path = require('path');
const APP_PATH = path.resolve(__dirname, '../app');
const DIST_PATH = path.resolve(__dirname, '../dist');
module.exports = {
    entry: {
        app: './app/index.js'
    },    
    output: {
        filename: 'js/bundle.js',
        path: DIST_PATH
    },
    module: {
        rules: [
            {
                test: /\.js?$/,
                use: "babel-loader",
                include: APP_PATH
            }
        ]
    }
};
複製程式碼
  • 執行 npm run build

新增外掛

  • public下的index.html本該自動新增到dist目錄,並且引用資源自動載入到該檔案,通過html-webpack-plugin實現這一步
$ npm install html-webpack-plugin --save-dev
複製程式碼
  • webpack.prod.conf.js中配置plugins屬性
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = merge(baseWebpackConfig, {
    mode: 'production',
    plugins: [
        new HtmlWebpackPlugin({
            template: 'public/index.html',
            inject: 'body',
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            },
        })
    ]
});
複製程式碼
  • 刪除 index.html 中手動引入的 script 標籤
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>從零開始搭建react工程</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
複製程式碼
  • 重新編譯檢視 npm run build 瀏覽器開啟檢視目錄 dist 下的 index.html

以上步驟都成功的前提下繼續走下一步

  • 生成的檔名新增Hash值,目的是解決快取問題
  • 修改webpack.prod.conf.js,mode: 'production', 增加以下程式碼
// webpack.prod.conf.js
output: {
    filename: "js/[name].[chunkhash:16].js",
},
複製程式碼
  • 生成前需要清理之前專案生成的檔案,因為由於檔名的改變如果不刪除會一直增加
  • 安裝外掛 clean-webpack-plugin
$ npm install --save-dev clean-webpack-plugin
複製程式碼
  • 修改 webpack.prod.conf.js
// webpack.prod.conf.js
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = merge(baseWebpackConfig, {
    mode: 'production',
    output: {
        filename: "js/[name].[chunkhash:16].js",
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'public/index.html',
            inject: 'body',
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            },
        }),
        new CleanWebpackPlugin(['../dist'], { allowExternal: true })
    ]
});
複製程式碼

公共程式碼與業務程式碼分離

  • 修改 webpack.base.conf.js 的 entry 入口屬性,抽出框架程式碼
entry: {
      app: './app/index.js',
      framework: ['react','react-dom'],
},
複製程式碼
  • 修改webpack.prod.conf.js,增加以下程式碼,目的是分離框架程式碼和業務程式碼
  • 雖然上面步驟抽出框架程式碼生成兩個檔案,但是app.js還是包含框架程式碼
optimization: {
        splitChunks: {
            chunks: "all",
            minChunks: 1,
            minSize: 0,
            cacheGroups: {
                framework: {
                    test: "framework",
                    name: "framework",
                    enforce: true
                }
            }
        }
    }
複製程式碼
  • cacheGroups物件,定義了需要被抽離的模組
  • 其中test屬性是比較關鍵的一個值,他可以是一個字串,也可以是正則表示式,還可以是函式。如果定義的是字串,會匹配入口模組名稱,會從其他模組中把包含這個模組的抽離出來
  • name是抽離後生成的名字,和入口檔案模組名稱相同,這樣抽離出來的新生成的framework模組會覆蓋被抽離的framework模組

整合 webpack-dev-server

  • 開發環境開啟服務監聽檔案改動實時更新最新內容
$ npm install --save-dev webpack-dev-server
複製程式碼
  • 在build中新增webpack.dev.conf.js檔案
const path = require('path');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = merge(baseWebpackConfig, {
    mode: 'development',
    output: {
        filename: "js/[name].[hash:16].js",
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'public/index.html',
            inject: 'body',
            minify: {
                html5: true
            },
            hash: false
        }),
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        port: '8080',
        contentBase: path.join(__dirname, '../public'),
        compress: true,
        historyApiFallback: true,
        hot: true,
        https: false,
        noInfo: true,
        open: true,
        proxy: {}
    }
});
複製程式碼
  • 在package.json scripts屬性新增內容
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
複製程式碼
  • npm run dev
  • 自動開啟瀏覽器開啟入口頁面實時更新

獨立匯出 css 檔案

  • 安裝css相關依賴
  • sass less 預處理
$ npm install extract-text-webpack-plugin
$ npm install style-loader css-loader postcss-loader autoprefixer --save-dev
$ npm install less sass less-loader sass-loader stylus-loader node-sass --save-dev
複製程式碼
  • webpack.base.conf.js 檔案修改
// webpack.base.conf.js
{
    test: /\.css$/,
    use: [
        {
          loader: "style-loader" //在html中插入<style>標籤
         },
         {
             loader: "css-loader",//獲取引用資源,如@import,url()
         },
         {
              loader: "postcss-loader",
              options: {
                    plugins:[
                       require('autoprefixer')({
                           browsers:['last 5 version']
                       })
                  ]
              }
        }
     ]
},
{
   test:/\.less$/,
   use: [
         {  loader: "style-loader"  },
         {  loader: "css-loader" },
         {
            loader: "postcss-loader",//自動加字首
            options: {
                   plugins:[
                          require('autoprefixer')({
                              browsers:['last 5 version']
                          })
                  ]
            }
         },
         {  loader: "less-loader" }
     ]
},
{
      test:/\.scss$/,
      use:[
             {  loader: "style-loader"  },
             {
                      loader: "css-loader",
             },
             {  loader: "sass-loader" },
             {
               loader: "postcss-loader",
              options: {
                    plugins:[
                         require('autoprefixer')({
                           browsers:['last 5 version']
                         })
                   ]
              }
          }
     ]
},
複製程式碼
  • 圖片和路徑處理
$ npm i file-loader url-loader --save-dev
複製程式碼
  • webpack.base.conf.js 檔案修改
// webpack.base.conf.js
{
    test: /\.(png|jpg|gif|woff|svg|eot|woff2|tff)$/,
    use: 'url-loader?limit=8129', 
    //注意後面那個limit的引數,當你圖片大小小於這個限制的時候,會自動啟用base64編碼圖片
    exclude: /node_modules/
}
複製程式碼

build 時報錯

Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead
    at Chunk.get (F:\react\createApp\node_modules\webpack\lib\Chunk.js:824:9)
複製程式碼
  • webpack4.0中使用“extract-text-webpack-plugin”報錯
  • 解決辦法
$ npm install [email protected]
複製程式碼

背景圖片路徑問題

  • 由於css檔案分離出來的原因,會導致在css資料夾下找images資料夾下的圖片
  • 解決辦法 publicPath屬性改為 '/',以絕對路徑的方式尋找資源
{
    test:/\.(png|jpg|gif)$/,
    use:[{
        loader:'url-loader',
        options: {
              // outputPath:'../',//輸出**資料夾
              publicPath: '/',
              name: "images/[name].[ext]",
              limit:500  //是把小於500B的檔案打成Base64的格式,寫入JS
         }
      }]
},
複製程式碼