webpack4 + react 搭建多頁面應用

滕偉發表於2019-02-16
webpack 升級到4之後還沒好好的同步一個可實用的webpack架子,接下來用webpack4來搭建一個簡單的react的多介面應用,廢話不說 直接擼碼

建立工程

$ mkdir demo && cd demo
$ npm init -y

目錄結構

![工程目錄結構][1]

webpack 配置

安裝react + babel 依賴

$ npm i -S react@16.3.0 react-dom@16.3.0

$ npm i -D webpack@4.4.1 webpack-cli@2.0.13 webpack-dev-server@3.1.1 webpack-merge@4.1.2 babel-cli@6.26.0 babel-preset-env@1.6.1 babel-preset-react@6.24.1 babel-preset-react-hmre@1.1.1 babel-loader@7.1.4 file-loader@1.1.11 url-loader@1.0.1

webpack.base.conf.js(config -> webpack)

const entry = require("./webpack.entry.conf");
const newEntry = {};
for (let name in entry) {
    newEntry[name] = entry[name][0]
}
let config = {
    entry: newEntry,
    resolve: {
        extensions: [".js", ".json", ".jsx", ".css", ".pcss"],
    }
};
module.exports = config;

webpack.dev.conf.js

const webpack = require(`webpack`);//引入webpack
const opn = require(`opn`);//開啟瀏覽器
const merge = require(`webpack-merge`);//webpack配置檔案合併
const path = require("path");
const baseWebpackConfig = require("./webpack.base.conf");//基礎配置
const webpackFile = require("./webpack.file.conf");//一些路徑配置
const eslintFormatter = require(`react-dev-utils/eslintFormatter`);

let config = merge(baseWebpackConfig, {
    /*設定開發環境*/
    mode: `development`,
    output: {
        path: path.resolve(webpackFile.devDirectory),
        filename: `js/[name].js`,
        chunkFilename: "js/[name].js",
        publicPath: ``
    },
    optimization: {
        runtimeChunk: {
            name: `manifest`
        },
        // 包拆分
        splitChunks: {
            cacheGroups: {
                common: {   // 專案的公共元件
                    chunks: "initial",
                    name: "common",
                    minChunks: 2,
                    maxInitialRequests: 5,
                    minSize: 0
                },
                vendor: {   // 第三方元件
                    test: /node_modules/,
                    chunks: "initial",
                    name: "vendor",
                    priority: 10,
                    enforce: true
                }
            }
        }
    },
    plugins: [
        /*設定熱更新*/
        new webpack.HotModuleReplacementPlugin(),
    ],
    module: {
        rules: [
            {
                test: /.(js|jsx)$/,
                use: [
                    `babel-loader`,
                    `cache-loader`,
                ],
                include: [
                    path.resolve(__dirname, "../../app"),
                    path.resolve(__dirname, "../../entryBuild")
                ],
                exclude: [
                    path.resolve(__dirname, "../../node_modules")
                ],
            },
            {
                test: /.(css|pcss)$/,
                loader: `style-loader?sourceMap!css-loader?sourceMap!postcss-loader?sourceMap`,
                exclude: /node_modules/
            },
            {
                test: /.(png|jpg|gif|ttf|eot|woff|woff2|svg|swf)$/,
                loader: `file-loader?name=[name].[ext]&outputPath=` + webpackFile.resource + `/`
            },
            {
                test: /.(js|jsx)$/,
                enforce: `pre`,
                use: [
                    {
                        options: {
                            formatter: eslintFormatter,
                            eslintPath: require.resolve(`eslint`),
                            // @remove-on-eject-begin
                            baseConfig: {
                                extends: [require.resolve(`eslint-config-react-app`)],
                            },
                            //ignore: false,
                            useEslintrc: false,
                            // @remove-on-eject-end
                        },
                        loader: require.resolve(`eslint-loader`),
                    },
                ],
                include: [
                    path.resolve(__dirname, "../../app")
                ],
                exclude: [
                    path.resolve(__dirname, "../../node_modules")
                ],
            }
        ]
    },
    /*設定api轉發*/
    devServer: {
        host: `0.0.0.0`,
        port: 8080,
        hot: true,
        inline: true,
        contentBase: path.resolve(webpackFile.devDirectory),
        historyApiFallback: true,
        disableHostCheck: true,
        proxy: [
            {
                context: [`/api/**`, `/u/**`],
                target: `http://10.8.200.69:8080/`,
                secure: false
            }
        ],
        /*開啟瀏覽器 並開啟本專案網址*/
        after() {
            opn(`http://localhost:` + this.port);
        }
    }
});
module.exports = config;

webpack.prod.conf.js

const path = require(`path`);
const merge = require(`webpack-merge`);
const HtmlWebpackPlugin = require(`html-webpack-plugin`);
const CopyWebpackPlugin = require(`copy-webpack-plugin`);
const CleanWebpackPlugin = require(`clean-webpack-plugin`);
const OptimizeCSSPlugin = require(`optimize-css-assets-webpack-plugin`);
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const baseWebpackConfig = require("./webpack.base.conf");
const webpackFile = require(`./webpack.file.conf`);
const entry = require("./webpack.entry.conf");
const webpackCom = require("./webpack.com.conf");

let config = merge(baseWebpackConfig, {
    /*設定生產環境*/
    mode: `production`,
    output: {
        path: path.resolve(webpackFile.proDirectory),
        filename: `js/[name].[chunkhash:8].js`,
        chunkFilename: "js/[name]-[id].[chunkhash:8].js",
    },
    optimization: {
        //包清單
        runtimeChunk: {
            name: "manifest"
        },
        //拆分公共包
        splitChunks: {
            cacheGroups: {
                common: { //專案公共元件
                    chunks: "initial",
                    name: "common",
                    minChunks: 2,
                    maxInitialRequests: 5,
                    minSize: 0
                },
                vendor: {   //第三方元件
                    test: /node_modules/,
                    chunks: "initial",
                    name: "vendor",
                    priority: 10,
                    enforce: true
                }
            }
        }
    },
    plugins: [
        // extract css into its own file
        new ExtractTextPlugin(`css/[name].[md5:contenthash:hex:8].css`),
        // Compress extracted CSS. We are using this plugin so that possible
        // duplicated CSS from different components can be deduped.
        new OptimizeCSSPlugin({
            assetNameRegExp: /.css$/g,
            cssProcessor: require(`cssnano`),
            cssProcessorOptions: {
                discardComments: {removeAll: true},
                // 避免 cssnano 重新計算 z-index
                safe: true
            },
            canPrint: true
        }),
    ],
    module: {
        rules: [
            {
                test: /.(js|jsx)$/,
                use: [
                    `babel-loader`,
                ],
            },
            {
                test: /.(js|jsx)$/,
                loader: `babel-loader`,
                exclude: /node_modules/,
            },
            {
                test: /.(css|pcss)$/,
                use: ExtractTextPlugin.extract({
                    fallback: "style-loader",
                    use: "css-loader!postcss-loader"
                })
            },
            {
                test: /.(png|jpg|gif|ttf|eot|woff|woff2|svg)$/,
                loader: `url-loader?limit=8192&name=[name].[hash:8].[ext]&publicPath=` + webpackFile.resourcePrefix + `&outputPath=` + webpackFile.resource + `/`
            },
            {
                test: /.swf$/,
                loader: `file?name=js/[name].[ext]`
            }
        ]
    }
});
let pages = entry;
for (let chunkName in pages) {
    let conf = {
        filename: chunkName + `.html`,
        template: `index.html`,
        inject: true,
        title: webpackCom.titleFun(chunkName,pages[chunkName][1]),
        minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeAttributeQuotes: true
        },
        chunks: [`manifest`, `vendor`, `common`, chunkName],
        hash: false,
        chunksSortMode: `dependency`
    };
    config.plugins.push(new HtmlWebpackPlugin(conf));
}
/* 清除 dist */
config.plugins.push(new CleanWebpackPlugin([webpackFile.proDirectory], {root: path.resolve(__dirname, `../../`), verbose: true, dry: false}));


/* 拷貝靜態資源  */
copyArr.map(function (data) {
    return config.plugins.push(data)
});


module.exports = config;

構建多介面

整體架構搭建起來之後
app -> component 

$ mkdir demo && cd demo

$ touch Index.jsx

    import React from `react`;

    class Index extends React.Component {
        render() {
            return (
                <div className="demo">
                    寫個demo
                </div>
            );
        }
    }
    
    export default Index;
    

在config -> entry

module.exports = [
    {
        name: `index`,
        path: `index/Index.jsx`,
        title: `首頁`,
        keywords: `首頁`,
        description: `首頁`
    },
    {
        name: `demo`,
        path: `demo/Index.jsx`,
        title: `demo`,
        keywords: `demo`,
        description: `demo`
    },
    {
        name: `demo1`,
        path: `demo1/Index.jsx`,
        title: `demo1`,
        keywords: `demo1`,
        description: `demo1`
    }
];


然後直接執行 npm run create-dev 就會在devBuild 和 entryBuild 中新增一個新的demo.html 和 demo.js

package.json

{
  "name": "webpack_es6",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --devtool eval --progress --colors --profile --config config/webpack/webpack.dev.conf.js",
    "entry": "node config/entry/entryBuild.js",
    "devBuildHtml": "node config/webpack/webpack.devBuildHtml.conf.js",
    "create-dev": "npm run entry && npm run devBuildHtml",
    "build": "BABEL_ENV=production && webpack --progress --colors --config config/webpack/webpack.prod.conf.js",
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^16.3.0",
    "react-dom": "^16.3.0"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-eslint": "^8.2.2",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-react-hmre": "^1.1.1",
    "cache-loader": "^1.2.2",
    "clean-webpack-plugin": "^0.1.19",
    "copy-webpack-plugin": "^4.5.1",
    "css-loader": "^0.28.11",
    "eslint": "^4.19.1",
    "eslint-config-react-app": "^2.1.0",
    "eslint-loader": "^2.0.0",
    "eslint-plugin-flowtype": "^2.46.1",
    "eslint-plugin-import": "^2.10.0",
    "eslint-plugin-jsx-a11y": "^5.1.1",
    "eslint-plugin-react": "^7.7.0",
    "extract-text-webpack-plugin": "^4.0.0-beta.0",
    "file": "^0.2.2",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.1.0",
    "optimize-css-assets-webpack-plugin": "^4.0.0",
    "postcss-cssnext": "^3.1.0",
    "postcss-loader": "^2.1.3",
    "precss": "^3.1.2",
    "react-dev-utils": "^5.0.0",
    "style-loader": "^0.20.3",
    "url-loader": "^1.0.1",
    "webpack": "^4.4.1",
    "webpack-cli": "^2.0.13",
    "webpack-dev-server": "^3.1.1",
    "webpack-merge": "^4.1.2"
  },
  "eslintConfig": {
    "extends": "react-app",
    "rules": {
      "import/no-webpack-loader-syntax": 0,
      "no-script-url": 0,
      "jsx-a11y/href-no-hash": 2
    }
  }
}

開發環境小技巧

在開發環境新增cache-loader 可以提升在開發環境的編譯速度

相關文章