webpack4多入口打包配置

一、简单介绍webpack

在现在的工作中,基本上都会使用到 webpack 这个打包工具。目前 webpack 的版本已经到了4.x,在官网说 webpack4.0.0 以上的,可以不去配置而直接使用 webpack 打包。

先安装 webpack 相关依赖:

 npm i webpack webpack-cli --save-dev

然后目录是这样子的:

然后 package.json 这样设置:

然后 npm run dev 或者 npm  run build 即可打包,实现了最简单的打包使用。

webpack 有四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)

入口:指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始;

输出:告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist

loader:让 webpack 拥有处理非 JavaScript 文件的能力

plugin:可以用于执行范围更广的任务,比如压缩,定义环境变量等

二、本次使用的webpack的目的

       之前使用 webpack 的是在 vue 开发中才有用到,不过大多数时候都是用 vue 脚手架去搭建整个项目,故 webpack 基本上是已经配置好了的,并不需要怎么去配置其内容。

       此次的 webpack 的配置构建是为了来打包多入口(多页面)的,学习如何配置 webpack

三、思路以及一些我认为比较需要注意的

1. 多入口

      对 webpack 配置的思路其实不难,本次要做的是多入口,那么很简单,官网有说到直接就在entry写多个键值对

const config = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js'
  }
}

       这样子就能实现多个入口了。但是多页面呢?思路就跟entry那里一样,在 plugins 这里 new 多个 html-webpakc-plugin ,就能够打包出来多个 html 页面了。那么问题来,如果每次多一个页面,不就得去 webpack 的配置里面多写一下入口和 new 一下 html-webpack-plugin 了,这太麻烦了。所以,就需要写一个额外的脚本来自动生成 entry 和 new htmlwebpackplugin()。

      要能够自动生成相对应的入口的 html 文件,就必须要先订好 src 下的目录规则。我这边约定好的规则:比如页面名字 test.html ,在 js 文件夹下则创建 test 为文件名的文件夹,然后创建唯一入口文件夹 index.js ,然后如果有多个其他的 js 文件,则在 index.js 内 import 进来。

      有了上面提到的规则,那么接下来就是需要遍历 src/pages 下的 html 文件,提取文件名,然后通过文件名,遍历得到js入口的对象,同时生成多个 html-webpack-plugin ,最后导出。遍历文件夹内部的文件,可以使用 glob 这个插件(跳到 glob 的 npm 页面),这个插件是用来匹配符合条件的文件,会把符合的以路径的形式返回出来给你。

      具体代码可查看下面源代码展示。

2. 代码分块

      webpack4 之前的代码分包是使用 CommonsChunkPlugin,在 webpack4 之后,是集成在内部了,在optimization.splitChunks 中配置代码分包。在配置这个之前,我还不清楚怎样分包,每次打包出来后,每个js文件都比原来的测试文件多出来10k左右的大小。后面查找官网,发现已经集成在了 optimization.splitChunks。在 splitChunks 中,它已经有了自己的默认配置:

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minRemainingSize: 0,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 6,
      maxInitialRequests: 4,
      automaticNameDelimiter: '~',
      automaticNameMaxLength: 30,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
}

       因为它的一些参数有默认的配置,故我这边 webpack 的 splitchunk 只配置一个 vendor,专门打包来自 node_modules 的依赖。

splitChunks: {
  cacheGroups: {
    // 打包node_modules中的文件
    vendor: {
      name: "vendor",
      test: /[\\/]node_modules[\\/]/,
      chunks: "all",
      priority: 10
    }
  }
}

     配置之后进行打包,业务代码的js体积跟原来的相比并没有多大变化,并多出来了个 vendor 的公共js文件

 

 

四、 webpack配置的源代码

本项目源代码:https://github.com/VintageLin/multiple_page_webpack

webpack 的代码结构如图:

文件解读:

config.CONSTANT.js: webpack 配置的一些常量

config.DEV.js: 开发环境调用的 webpack 配置

config.entries.js: webpack 入口及 HTML 模板插件生成

config.PROD.js: 生产环境调用的 webpack 打包配置

webpack.config.js: webpack 的一些通用配置

照例贴上代码,代码里面已经写好了一些注释,并配合 github 项目的 README.md 文档,方便理解

config.CONSTANT.js:

module.exports = {
    INITIAL_PORT: 9000,        // 初始端口
    STOP_PORT: 9999,           // 结束端口号
    HOST: 'localhost'          // 使用一个 host,如果外部需要访问则变成0.0.0.0
}

 config.DEV.js:

const merge = require('webpack-merge')    // webpack设置合并插件
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')   // 使webpack在控制台输出更友好
const portfinder = require('portfinder')  // 端口寻找
const chalk = require('chalk')            // 用来美化控制台输出
const log = console.log

const webpackUniversalConfig = require('./webpack.config.js')
const CONSTANT = require('./config.CONSTANT')

const config = {
  mode: 'development',
  devServer: {
    compress: true,               // gzip压缩
    port: CONSTANT.INITIAL_PORT,  // 端口
    hot: true,                    // hot reload
    overlay: true,                // 出现编译器错误或警告时,在浏览器中显示全屏覆盖层
    stats: 'minimal',
    quiet: true,
    host: CONSTANT.HOST
  },
  plugins: []
}

module.exports = new Promise((resolve, reject) => {
  // 端口查找
  portfinder.getPort({
    port: CONSTANT.INITIAL_PORT,           // 起始
    stopPort: CONSTANT.STOP_PORT           // 结束
  }, function (err, res) {
    if (err) {
      log(chalk.red('在当前设定的区间无可使用的端口!'))
      process.exit()
    } else {
      log(chalk.green('当前DEV可运行的端口:', res))
      config.devServer.port = res
      config.plugins.push(new FriendlyErrorsWebpackPlugin({
        compilationSuccessInfo: {
          messages: [`当前项目运行在 http://${CONSTANT.HOST}:${res}`]
        }
      }))
      // 合并设置
      const config_DEV = merge(config, webpackUniversalConfig)
      resolve(config_DEV)
    }
  })
})

config.entries.js:

const glob = require('glob')            // 用来获取匹配相对应规则的文件
const htmlWebpackPlugin = require('html-webpack-plugin')      // 输出html文件

// 遍历页面寻找src/js/ 下的index.js
let htmlArray = glob.sync('./src/pages/*.html')
let jsEntries = {}
let pagesName = htmlArray.map(item => {
    let newItem = item.slice(item.lastIndexOf('/') + 1)
    newItem = newItem.replace('.html', '')
    jsEntries[newItem] = [`./src/js/${newItem}/index.js`]
    return newItem
})

// 生成html模板
let htmlWebpackPluginArray = []
pagesName.forEach(item => {
    htmlWebpackPluginArray.push(new htmlWebpackPlugin({
        filename: `${item}.html`,                   // 保存的文件名
        template: `./src/pages/${item}.html`,       // 生成后的文件保存位置
        chunks: ['vendor', item]                    // 这里是你页面需要引用到的js
    }))
})

let entries = {
    jsEntries,
    htmlWebpackPluginArray
}
module.exports = entries 

config.PROD.js:

const { CleanWebpackPlugin } = require("clean-webpack-plugin")     // 清空打包文件夹内容
const merge = require('webpack-merge')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')  // 压缩css插件
const UglifyWebpackPlugin = require('uglifyjs-webpack-plugin')    // 压缩js插件
const webpackUniversalConfig = require("./webpack.config.js")

const config = {
  mode: 'production',
  output: {
    // 公共目录定位到当前文件夹下
    publicPath: './'
  },
  performance: {
    hints: false 
  },
  plugins: [
    // 每次打包前清空打包目标文件夹
    new CleanWebpackPlugin()
  ],
  optimization: {
    minimizer: [
      // js压缩
      new UglifyWebpackPlugin({
        parallel: 4
      }),
      // css压缩
      new OptimizeCssAssetsPlugin({
        assetNameRegExp: /\.css/,       // 需要优化压缩的文件名
        cssProcessor: require('cssnano'),
        cssProcessorPluginOptions: {
          preset: ['default', { discardComments: { removeAll: true } }],
        },
        canPrint: true
      })
    ],
    // 代码拆分
    splitChunks: {
      cacheGroups: {
        // 打包node_modules中的文件
        vendor: {
          name: "vendor",
          test: /[\\/]node_modules[\\/]/,
          chunks: "all",
          priority: 10
        }
      }
    }
  }
}

const config_PROD = merge(config, webpackUniversalConfig)
module.exports = config_PROD

webpack.config.js:

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')    // 提取css到文件,并且在head里面插入link标签引用
const entries = require('./config.entries')

module.exports = {
  entry: entries.jsEntries,
  output: {
    path: path.resolve(__dirname, '../build'),
    filename: 'js/[name].[hash].js',
    chunkFilename: 'js/[name].js'
  },
  // 控制台打印输出配置
  stats: {
    entrypoints: false,
    chunks: false,
    children: false,
    chunkModules: true,
    modules: false
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../',
              hmr: process.env.NODE_ENV === 'development',
            },
          },
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              plugins: [
                require("autoprefixer")
              ]
            }
          }
        ],
      },
      {
        test: /\.(jpg|jpeg|png|gif)$/,
        loader: 'url-loader',
        options: {
          name: 'images/[name].[hash].[ext]',
          limit: 10000
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader?cacheDirectory=true', 'eslint-loader']
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      }
    ]
  },
  resolve: {
    // 模块别名,方便import
    alias: {
      '@': path.resolve(__dirname, './src')
    },
    // 自动解析扩展名
    extensions: [".js", ".json"]
  },
  plugins: [
    // 页面模板
    ...entries.htmlWebpackPluginArray,
    // 抽离css到单独文件
    new MiniCssExtractPlugin({
      filename: 'css/[name].css?[hash]',
      chunkFilename: '[id].[hash].css',
      ignoreOrder: false
    })
  ]
}

     以上为主要的 webpack 配置,具体还是移步 github

附:一些遇到的小问题

1. 打包后引用路径不正确,这里的设置公共目录的位置,不能在 webpack.config.js 里面写成通用的,否则就会引起开发环境和生产环境的一些目录差异,因为生产环境的可能是多级目录下访问这个页面,所以只能在 config.PROD.js 里面定义,开发环境则为默认配置。感觉这个也不算是个问题,是我自己没理解好就写到通用配置文件里面了。

2. 在 webpack4 之前是使用 extract-text-webpack-plugin,在webpack4 之后是使用 mini-css-extract-plugin,一开始没仔细看文档,也没去 npm 官网搜下,拿来直接就用了,然后就发生错误,忘记截图目前官网已经推荐使用 mini-css-extract-plugin 了,npm 搜索后也有提示。这个问题也是属于自己没有好好查看文档造成的

 

3. 去除打包后的 console 和 debugger 等

4. 上面有说到代码分块,代码分块后,还得把公共代码引用到页面上去,这里就得在 html-webpack-plugin 里面的 chunks 参数设置,比如说这样子

最后暂未解决的问题:

实现修改 html 文件,自动刷新当前页面

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章