webpack 4.x 入門(mén)

webpack都更新到4.17.x了,官網(wǎng)的文檔有一部分還停留在4.x以下,因此寫(xiě)下這篇文章,以便后續(xù)遇到坑的時(shí)候再次抓耳撓腮。

項(xiàng)目地址: webpack4-skeleton

目錄結(jié)構(gòu)

├── dist                      打包輸出目錄
├── node_modules              npm安裝的依賴(lài)包目錄
├── public                    靜態(tài)資源目錄
│   ├── static                第三方靜態(tài)文件目錄
│   │   └── images            靜態(tài)圖片目錄
│   ├── favicon.ico           網(wǎng)頁(yè)圖標(biāo)
│   └── index.html            入口html
├── src                       開(kāi)發(fā)源代碼目錄
│   ├── assets                靜態(tài)文件目錄
│   │   ├── css               樣式文件目錄
│   │   ├── fonts             字體目錄
│   │   ├── img               靜態(tài)圖片目錄
│   │   └── js                公共函數(shù)目錄
│   ├── components            可以復(fù)用的模塊目錄
│   ├── pages                 頁(yè)面存放目錄
│   └── index.js              入口js
├── webpack-config            webpack配置目錄
│   ├── common.js             配置文件入口
│   ├── dev.js                開(kāi)發(fā)環(huán)境配置
│   └── prod.js               生產(chǎn)環(huán)境配置
├── .eslintignore             ESlint忽略文件配置信息
├── .eslintrc.js              ESlint配置信息
├── babel.config.js           babel配置信息
└── package.json              項(xiàng)目配置信息

初始化項(xiàng)目

npm init

按照提示配置項(xiàng)目的基礎(chǔ)信息后,即可在項(xiàng)目文件夾下到生成了package.json文件。

用ESlint檢查語(yǔ)法規(guī)范

首先安裝必要的package

npm i -D eslint eslint-config-airbnb-base babel-eslint eslint-loader eslint-plugin-html eslint-plugin-import

由于airbnb的規(guī)范過(guò)于嚴(yán)格,創(chuàng)建.eslintrc.js,創(chuàng)建自定義配置文件。

module.exports = {
  extends: "airbnb-base", // 繼承airbnb的語(yǔ)法檢測(cè)規(guī)則
  root: true,
  env: {
    node: true,
    browser: true,        // 檢測(cè)環(huán)境基于瀏覽器,避免在調(diào)用window對(duì)象的時(shí)候報(bào)錯(cuò)
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'strict': 0,
    'no-param-reassign': ["error", {
      "props": false,               // 允許給函數(shù)的參數(shù)重新賦值
    }],
    'max-len': ["error", {
      "code": 240,                  // 允許單行代碼最長(zhǎng)為240個(gè)字符
    }],
    'no-plusplus': ["error", {
      "allowForLoopAfterthoughts": true, // 允許在for循環(huán)中用 i++寫(xiě)法
    }],
    'no-new': 'off',                     // 允許new Object()
    'class-methods-use-this': 'off',     // 允許class中的function內(nèi)不使用this
    'import/no-unresolved': 0,           // 避免webpack指定alias,后eslint報(bào)錯(cuò)
  },
  plugins: [
    'html'
  ],
  parser: 'babel-eslint'
};

接下來(lái)創(chuàng)建.eslintignore,忽略開(kāi)發(fā)時(shí)源代碼之外的文件。

/node_modules/
/webpack-config/
/dist/
/public/
/static/
/*.js

安裝webpack、babel和必要的webpack loader及webpack plugin

npm i -D webpack webpack-cli webpack-dev-server "@babel/core" "@babel/plugin-syntax-dynamic-import" "@babel/plugin-transform-async-to-generator" "@babel/plugin-transform-regenerator" "@babel/plugin-transform-runtime" "@babel/preset-env" "@babel/runtime" babel-loader css-loader file-loader html-loader@next html-webpack-plugin@next less less-loader regenerator-runtime style-loader url-loader

html-webpack-plugin@next 安裝最新的alpha版本是為了解決以下bug:

Fail to inject code-splitting script files when webpack 4 optimization.splitChunks.name: false

html-loader@next 安裝最新的alpha版本是因?yàn)閣ebpack 4.x已經(jīng)禁用了html-loader的htmlLoader選項(xiàng),安裝alpha版可以Disable HTML Assets。

配置webpack的開(kāi)發(fā)環(huán)境

首先安裝必要的package

npm i -D copy-webpack-plugin friendly-errors-webpack-plugin webpack-merge

  • copy-webpack-plugin 用來(lái)拷貝靜態(tài)資源
  • friendly-errors-webpack-plugin 能夠更好在終端看到webapck運(yùn)行的警告和錯(cuò)誤
  • webpack-merge 用來(lái)合并多個(gè)webpack的配置文件

然后創(chuàng)建配置文件common.jsdev.js、prod.js,common.js存放開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境公用的配置:

const { resolve } = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const merge = require('webpack-merge');

const prodConfig = require('./prod.js');
const devConfig  = require('./dev.js');

const commonConfig = {
  output: {
    filename: 'js/[name].js',
    chunkFilename: 'js/[name].[chunkhash].js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: 'babel-loader',
        }, {
          loader: 'eslint-loader',
        }],
      }, {
        test: /\.html$/,
        use: [{
          loader: 'html-loader',
          options: {
            url: false,
          }
        }],
      }, {
        test: /\.(png|jpg|jpeg|gif|bmp)(\?.+)?$/,
        include: [resolve(__dirname, '../src/assets')],
        exclude: [resolve(__dirname, '../public/static')],
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 20480,
              name: '[name].[ext]',
              outputPath: 'img',
            },
          },
        ],
      }, {
        test: /\.(eot|ttf|woff|woff2|svg)(\?.+)?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[ext]',
              outputPath: 'fonts',
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new CopyWebpackPlugin([{
      from: resolve(__dirname, '../public/static'),
      to: resolve(__dirname, '../dist/static'),
    }]),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.NamedModulesPlugin(),
    new FriendlyErrorsWebpackPlugin(),
  ],
  resolve: {
    extensions: ['.js', '.css', '.less'],
    alias: {
      '@': resolve(__dirname, '../src'),
      'assets': resolve(__dirname, '../src/assets'),
    },
  },
};

module.exports = function(env, argv) {
  const config = argv.mode === 'production' ? prodConfig : devConfig;
  return merge(commonConfig, config);
};

接下來(lái)配置開(kāi)發(fā)環(huán)境dev.js:

const { resolve } = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  output: {
    path: resolve(__dirname, '../dist'),
    publicPath: '',
  },
  module: {
    rules: [
      {
        test: /\.(le|c)ss$/,
        use: ['style-loader', 'css-loader', 'less-loader'],
      }
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      favicon: 'public/favicon.ico',
      filename: 'index.html',
    }),
    new webpack.HotModuleReplacementPlugin(),
  ],
  performance: {
    hints: false,
  },
  stats: 'errors-only',
  devServer: {
    contentBase: '../dist',
    hot: true,
    host: 'localhost',
    port: 8050,
    stats: 'errors-only',
    overlay: true,
  }
};

修改package.json,添加script腳本:

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --open --progress --config webpack-config/common.js",
    "build": "webpack --mode production --progress --config webpack-config/common.js"
  },
}

執(zhí)行npm run dev就會(huì)看到瀏覽器自動(dòng)加載頁(yè)面,如果現(xiàn)在修改和保存任意源文件,web 服務(wù)器就會(huì)自動(dòng)重新加載編譯后的代碼,這意味著開(kāi)發(fā)環(huán)境就配置完成了。

寫(xiě)幾個(gè)頁(yè)面跑起來(lái)

開(kāi)始擼頁(yè)面代碼,具體可以查看項(xiàng)目代碼webpack4-skeleton,略過(guò)......

配置webpack的生產(chǎn)環(huán)境

首先安裝必要的package

npm i -D mini-css-extract-plugin uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin clean-webpack-plugin

  • mini-css-extract-plugin 從 bundle 中提取css到單獨(dú)的文件
  • optimize-css-assets-webpack-plugin 優(yōu)化css打包
  • uglifyjs-webpack-plugin 控制項(xiàng)目中 UglifyJS
  • clean-webpack-plugin 清除上次打包的文件

編輯prod.js

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  mode: 'production',
  devtool: 'none',
  output: {
    path: resolve(__dirname, '../dist/static'),
    publicPath: './static/', // 使用相對(duì)路徑而不是絕對(duì)路徑可以避免在hybird App框架中因?yàn)檎也坏铰窂綀?bào)錯(cuò)
  },
  module: {
    rules: [
      {
        test: /\.(le|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../',
            },
          },
          'css-loader',
          'less-loader',
        ],
      }
    ],
  },
  plugins: [
    new CleanWebpackPlugin(['dist/*'], {
      root: resolve(__dirname, '../'), // 指定root可以避免由于清理目錄超出根目錄而跳過(guò)清理
    }),
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      favicon: 'public/favicon.ico',
      filename: '../index.html',
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
      chunkFilename: "css/[name].[chunkhash].css",
    }),
  ],
  optimization: {
    runtimeChunk: true,
    splitChunks: {
      chunks: 'all',
      name: true,
    },
    minimizer: [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: false, // set to true if you want JS source maps
      }),
      new OptimizeCSSAssetsPlugin({})
    ],
  },
  performance: {
    hints: 'warning',
    assetFilter: function(assetFilename) {
      return assetFilename.endsWith('.js') ||  assetFilename.endsWith('.css');
    },
  },
  stats: 'errors-only',
};

執(zhí)行npm run build,打包生產(chǎn)環(huán)境的代碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容