详解Webpack4多页应用打包方案


Posted in Javascript onJuly 16, 2020

前言

学习了 webpack 之后,将自己的博客的前端进行重构,由于自己的博客是多页应用,所以研究了下多页应用的打包方案。在这里把最后配置的成果分享下,不足之处,请指正。(文字不多,全是代码,不是配置教程,所以没有特别详细的写,只是一个参考)

项目地址: https://github.com/Ray-daydayup/MPA-webpack

文件目录结构

项目目录结构

首先先看下我的项目的目录结构

myblog-webpack
 ├── dist           // 打包输出文件夹
 ├── src           // 源代码
 │  ├── api         // 请求文件夹,存放封装的请求方法
 │  ├── assets        // 静态资源文件夹
 │  ├── lib         // 一些库
 │  ├── pages        // 页面
 │  │  ├── about      // 页面名称
 │  │  │  ├── index.html  // html模板
 │  │  │  └── index.js   // 入口js
 │  │  ├── category
 │  │  │  ├── index.html
 │  │  │  └── index.js
 │  │  ├── detail
 │  │  │  ├── index.html
 │  │  │  └── index.js
 │  │  ├── index
 │  │  │  ├── index.html
 │  │  │  └── index.js
 │  │  └── tag
 │  │    ├── index.html
 │  │    └── index.js
 │  ├── styles       // 样式文件
 │  └── utils        // 工具函数
 ├── package.json
 ├── README.md
 ├── package-lock.json
 ├── webpack.base.js    // 公共配置
 ├── webpack.dev.js     // 开发环境配置
 └── webpack.prod.js    // 生产环境配置

打包输出文件目录结构

最终打包输出的目录如下

dist
 ├── assets
 ├── css
 │  ├── commons672a1e57.css
 │  └── index6085d612.css
 ├── js
 │  ├── aboutfc723f0e.js
 │  ├── categorye4be3bd6.js
 │  ├── commons78f1dd3f.js
 │  ├── detail0df434c5.js
 │  ├── indexe1e985d9.js
 │  ├── markdown-it
 │  │  ├── highlight.vendors.js
 │  │  ├── markdown-it-integrated.js
 │  │  ├── markdown-it-integrated.min.js
 │  │  ├── markdown-it-integrated.min.js.LICENSE.txt
 │  │  └── markdown-it.vendors.js
 │  └── tagf1c1035c.js
 ├── about.html
 ├── category.html
 ├── detail.html
 ├── favicon.ico
 ├── index.html
 └── tag.html

webpack 配置文件

注意:文中各部分所需的包名,并没有书写,请在附录中查找!!!相关配置的意义可以查找官方文档

公共配置

动态获取 entry 和设置 html-webpack-plugin

多页应用的打包思路是 每个页面对应一个 entry ,一个 html-webpack-plugin ,但每次新增或删除页面需要改 webpack 配置,所以需要根据文件目录动态获取入口,来设置 html-webpack-plugin 。

利用一个库 glob 来实现对文件目录的读取 具体代码如下,相关配置的意义可以查找官方文档

const path = require('path')
const glob = require('glob')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const setMPA = () => {
 const entry = {}
 const htmlWebpackPlugins = []
 const entryFiles = glob.sync(path.join(__dirname, './src/pages/*/index.js')) // 匹配各页面的对应入口文件
 entryFiles.forEach((item) => {
  const pageName = item.match(/pages\/(.*)\/index.js/)[1] // 获取文件夹名,即页面名称
  entry[pageName] = item                  // 设置入口文件路径
  // 设置html-webpack-plugin数组
  htmlWebpackPlugins.push(
   new HtmlWebpackPlugin({
    template: path.join(__dirname, `./src/pages/${pageName}/index.html`),//模板地址
    filename: `${pageName}.html`, //输出文件名
    chunks: [pageName], // 插入的js chunk名称,和output有关
    inject: true,    
    favicon: path.join(__dirname, './src/assets/favicon.ico'), // 图标
    minify: { //压缩代码
     html5: true,
     collapseWhitespace: true,
     preserveLineBreaks: false,
     minifyCSS: true,
     minifyJS: true,
     removeComments: false
    }
   })
  )
 })
 return {
  entry,
  htmlWebpackPlugins
 }
}

const { entry, htmlWebpackPlugins } = setMPA()

使用 ES6 和 async、await ,以及 eslint

使用 ES6 和 async、await 以及 eslint ,需要借助 babel-loader 和 eslint-loader ,其具体配置很简单

babel 相关的库: @babel/core 、 @babel/preset-env 、 @babel/plugin-transform-regenerator 、 @babel/plugin-transform-runtime 、 eslint 相关的库: babel-eslint 、 eslint

module.rules 的配置

{
  test: /\.js$/,
  use: ['babel-loader', 'eslint-loader']
}

.babelrc 文件的配置

{
 "presets": [
  "@babel/preset-env"
 ],
 "plugins": [ //设置支持async和await
  "@babel/plugin-transform-runtime",
  "@babel/plugin-transform-regenerator"
 ]
}

.eslintrc.js 的配置

module.exports = {
 parser: 'babel-eslint',
 extends: ['alloy'], // eslint标准请自行选择下载
 env: {
  browser: true,
  node: true
 },
 rules: {
  'no-new': 'off',
  'no-proto': 'off'
  // 'no-console': 'error'
 }
}

加载图片

利用 url-loader ,具体 module.rules 配置如下

{
  test: /\.(png|svg|jpg|gif)$/,
  use: [
   {
    loader: 'url-loader',
    options: {
     esModule: false,
     name: '[name][hash:8].[ext]', //文件指纹
     limit: 10240,         // 转base64
     outputPath: './assets/images/' // 图片文件输出目录和publicPath有关
    }
   }
  ]
 }

清理打包目录和复制文件到打包目录

自动清理打包目录利用 clean-webpack-plugin 插件,复制文件利用 copy-webpack-plugin

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

const basePlugins = [
 ...htmlWebpackPlugins, //之前配置的htmlWebpackPlugin数组
 new CleanWebpackPlugin(), // 自动清理构建目录
 new CopyWebpackPlugin({
  patterns: [
   {
    from: path.join(__dirname, 'src/lib/markdown-it'), // 源
    to: path.join(__dirname, 'dist/js/markdown-it') // 目标位置
   }
  ]
 })
]

.browserslistrc 配置

# Browsers that we support

last 2 version
> 1%
iOS 7

开发环境配置

热更新以及 devServer

具体配置如下:

plugins: [...basePlugins, new webpack.HotModuleReplacementPlugin()], //热更新插件
devServer: {
  contentBase: 'dist', // 开启服务的目录
  hot: true, //热更新开启
  proxy: {
   '/api': {
    target: 'http://raydaydayup.cn:3000', //代理
    pathRewrite: { '^/api': '' }
   }
  }
}

CSS 相关配置

module.rules 的配置如下

{
  test: /\.less$/,
  use: ['style-loader', 'css-loader', 'less-loader']
},
{
  test: /\.css$/,
  use: ['style-loader', 'css-loader']
}

出口

output: {
  filename: '[name].js',
  path: path.join(__dirname, 'dist')
}

生产环境配置

出口

output: {
  filename: 'js/[name][chunkhash:8].js', // 统一输出到js文件夹
  path: path.join(__dirname, 'dist'),
  publicPath: '/' // 服务路径
}

js 代码压缩

利用 terser-webpack-plugin 插件,配置 optimization

const TerserPlugin = require('terser-webpack-plugin')

optimization: {
  minimize: true,
  minimizer: [
   new TerserPlugin({
    include: [/\.js$/]
   })
  ]
}

CSS 相关配置

CSS 代码压缩,利用 optimize-css-assets-webpack-plugin 和 cssnano

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

plugins: [
 new OptimizeCSSAssetsPlugin({
   assetNameRegExp: /\.css$/g,
   cssProcessor: require('cssnano')
  })
]

CSS 分离单独文件,利用 mini-css-extract-plugin

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module: {
 rules: [
   ...baseModuleRules,
   {
    test: /\.less$/,
    use: [
     {
      loader: MiniCssExtractPlugin.loader,
      options: {
       esModule: true
      }
     },
     'css-loader',
     'less-loader'
    ]
   },
   {
    test: /\.css$/,
    use: [
     {
      loader: MiniCssExtractPlugin.loader,
      options: {
       esModule: true
      }
     },
     'css-loader'
    ]
   }
  ]
},
plugins: [
  ...basePlugins,
  new MiniCssExtractPlugin({
   filename: 'css/[name][contenthash:8].css' // 输出文件名和地址
  }),
 ],

PostCSS 插件 autoprefixer 自动补齐 CSS3 前缀

module: {
  rules: [
   ...baseModuleRules,
   {
    test: /\.less$/,
    use: [
     {
      loader: MiniCssExtractPlugin.loader,
      options: {
       esModule: true
      }
     },
     'css-loader',
     {
      loader: 'postcss-loader',
      options: {
       plugins: () => [require('autoprefixer')]
      }
     },
     'less-loader'
    ]
   },
  ]
 },

分离公共文件(CSS和JS)

配置 optimization.splitChunks

optimization: {
  splitChunks: {
   cacheGroups: {
    commons: {
     name: 'commons',
     chunks: 'all',
     minChunks: 2 // 最少引用两次
    }
   }
  }
 }

使用相关注意

html-webpack-plugin 模板使用以及 html-loader

导入 html 片段,在相应位置写下面的代码就像,需要注意的是, html-loader 加载的 html 片段内部 <%%> 语法会失效

<%= require("html-loader!@/components/recent.html") %>

html 中图片的加载。在 html 中相关的 img 标签的图片, html-loader 加载时会自动调用 url-loader ,但是存在问题,加载出来的路径没有 "" 。为了避免这个问题,我就没有再 html-loader 加载的 html 片段中使用图片,而是直接再 html 模

板中直接用 require

<img src="<%= require('@/assets/logo362x82.png') %>" alt="" />

附录

配置文件完整版

webpack.base.js

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

const path = require('path')
const glob = require('glob')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

const setMPA = () => {
 const entry = {}
 const htmlWebpackPlugins = []
 const entryFiles = glob.sync(path.join(__dirname, './src/pages/*/index.js'))
 entryFiles.forEach((item) => {
  const pageName = item.match(/pages\/(.*)\/index.js/)[1]
  entry[pageName] = item
  htmlWebpackPlugins.push(
   new HtmlWebpackPlugin({
    template: path.join(__dirname, `./src/pages/${pageName}/index.html`),
    filename: `${pageName}.html`,
    chunks: [pageName],
    inject: true,
    favicon: path.join(__dirname, './src/assets/favicon.ico'),
    minify: {
     html5: true,
     collapseWhitespace: true,
     preserveLineBreaks: false,
     minifyCSS: true,
     minifyJS: true,
     removeComments: false
    }
   })
  )
 })
 return {
  entry,
  htmlWebpackPlugins
 }
}

const { entry, htmlWebpackPlugins } = setMPA()

const baseModuleRules = [
 {
  test: /\.js$/,
  use: ['babel-loader', 'eslint-loader']
 },
 {
  test: /\.(png|svg|jpg|gif)$/,
  use: [
   {
    loader: 'url-loader',
    options: {
     esModule: false,
     name: '[name][hash:8].[ext]',
     limit: 10240,
     outputPath: './assets/images/'
    }
   }
  ]
 }
]

const basePlugins = [
 ...htmlWebpackPlugins,
 new CleanWebpackPlugin(),
 new CopyWebpackPlugin({
  patterns: [
   {
    from: path.join(__dirname, 'src/lib/markdown-it'),
    to: path.join(__dirname, 'dist/js/markdown-it')
   }
  ]
 })
]
module.exports = {
 entry,
 baseModuleRules,
 basePlugins
}

webpack.dev.js

const webpack = require('webpack')
const path = require('path')
const { entry, baseModuleRules, basePlugins } = require('./webpack.base.js')

module.exports = {
 mode: 'development',
 entry,
 output: {
  filename: '[name].js',
  path: path.join(__dirname, 'dist')
 },
 module: {
  rules: [
   ...baseModuleRules,
   {
    test: /\.less$/,
    use: ['style-loader', 'css-loader', 'less-loader']
   },
   {
    test: /\.css$/,
    use: ['style-loader', 'css-loader']
   }
  ]
 },
 resolve: {
  // 设置别名
  alias: {
   '@': path.join(__dirname, 'src') // 这样配置后 @ 可以指向 src 目录
  }
 },
 plugins: [...basePlugins, new webpack.HotModuleReplacementPlugin()],
 devServer: {
  contentBase: 'dist',
  hot: true,
  proxy: {
   '/api': {
    target: 'http://raydaydayup.cn:3000',
    pathRewrite: { '^/api': '' }
   }
  }
 }
}

webpack.prod.js

const { entry, baseModuleRules, basePlugins } = require('./webpack.base.js')
const path = require('path')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
 mode: 'production',
 entry,
 output: {
  filename: 'js/[name][chunkhash:8].js',
  path: path.join(__dirname, 'dist'),
  publicPath: '/'
 },
 resolve: {
  // 设置别名
  alias: {
   '@': path.resolve('src') // 这样配置后 @ 可以指向 src 目录
  }
 },
 module: {
  rules: [
   ...baseModuleRules,
   {
    test: /\.less$/,
    use: [
     {
      loader: MiniCssExtractPlugin.loader,
      options: {
       esModule: true
      }
     },
     'css-loader',
     {
      loader: 'postcss-loader',
      options: {
       plugins: () => [require('autoprefixer')]
      }
     },
     'less-loader'
    ]
   },
   {
    test: /\.css$/,
    use: [
     {
      loader: MiniCssExtractPlugin.loader,
      options: {
       esModule: true
      }
     },
     'css-loader'
    ]
   }
  ]
 },
 plugins: [
  ...basePlugins,
  new MiniCssExtractPlugin({
   filename: 'css/[name][contenthash:8].css'
  }),
  new OptimizeCSSAssetsPlugin({
   assetNameRegExp: /\.css$/g,
   cssProcessor: require('cssnano')
  })
 ],
 optimization: {
  minimize: true,
  minimizer: [
   new TerserPlugin({
    include: [/\.js$/]
   })
  ],
  splitChunks: {
   cacheGroups: {
    commons: {
     name: 'commons',
     chunks: 'all',
     minChunks: 2
    }
   }
  }
 }
}

package.json文件

{
 "name": "myblog-webpack",
 "version": "1.0.0",
 "description": "",
 "main": ".eslintrc.js",
 "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "webpack --config webpack.prod.js",
  "dev": "webpack-dev-server --config webpack.dev.js --open"
 },
 "keywords": [],
 "author": "",
 "license": "ISC",
 "devDependencies": {
  "@babel/core": "^7.10.2",
  "@babel/plugin-transform-regenerator": "^7.10.3",
  "@babel/plugin-transform-runtime": "^7.10.3",
  "@babel/preset-env": "^7.10.2",
  "autoprefixer": "^9.8.4",
  "babel-eslint": "^10.1.0",
  "babel-loader": "^8.1.0",
  "clean-webpack-plugin": "^3.0.0",
  "copy-webpack-plugin": "^6.0.3",
  "css-loader": "^3.6.0",
  "cssnano": "^4.1.10",
  "eslint": "^7.2.0",
  "eslint-config-alloy": "^3.7.2",
  "eslint-loader": "^4.0.2",
  "file-loader": "^6.0.0",
  "glob": "^7.1.6",
  "html-loader": "^1.1.0",
  "html-webpack-plugin": "^4.3.0",
  "less-loader": "^6.1.2",
  "mini-css-extract-plugin": "^0.9.0",
  "optimize-css-assets-webpack-plugin": "^5.0.3",
  "postcss-loader": "^3.0.0",
  "style-loader": "^1.2.1",
  "terser-webpack-plugin": "^3.0.5",
  "url-loader": "^4.1.0",
  "webpack": "^4.43.0",
  "webpack-cli": "^3.3.11",
  "webpack-dev-server": "^3.11.0"
 },
 "dependencies": {
  "axios": "^0.19.2"
 }
}

到此这篇关于详解Webpack4多页应用打包方案的文章就介绍到这了,更多相关Webpack4多应用打包内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
js RuntimeObject() 获取ie里面自定义函数或者属性的集合
Nov 23 Javascript
jquery获取checkbox的值并post提交
Jan 14 Javascript
js使用Array.prototype.sort()对数组对象排序的方法
Jan 28 Javascript
基于jQuery Circlr插件实现产品图片360度旋转
Sep 20 Javascript
js获取本机操作系统类型的两种方法
Dec 19 Javascript
JavaScript学习笔记之数组求和方法
Mar 23 Javascript
js日期相关函数dateAdd,dateDiff,dateFormat等介绍
Sep 24 Javascript
简单谈谈gulp-changed插件
Feb 21 Javascript
关于angularJs清除浏览器缓存的方法
Nov 28 Javascript
Vue.js 十五分钟入门图文教程
Sep 12 Javascript
如何实现iframe父子传参通信
Feb 05 Javascript
Vue和Flask通信的实现
May 19 Vue.js
快速了解Vue父子组件传值以及父调子方法、子调父方法
Jul 15 #Javascript
微信小程序12行js代码自己写个滑块功能(推荐)
Jul 15 #Javascript
TypeScript 引用资源文件后提示找不到的异常处理技巧
Jul 15 #Javascript
微信小程序实现列表的横向滑动方式
Jul 15 #Javascript
JavaScript之scrollTop、scrollHeight、offsetTop、offsetHeight等属性学习笔记
Jul 15 #Javascript
JavaScript实时更新当前的时间的示例代码
Jul 15 #Javascript
jQuery 添加元素和删除元素的方法
Jul 15 #jQuery
You might like
PHP对字符串的递增运算分析
2010/08/08 PHP
Laravel执行migrate命令提示:No such file or directory的解决方法
2016/03/16 PHP
php通过各种函数判断0和空
2020/07/04 PHP
PHP 二维array转换json的实例讲解
2018/08/21 PHP
JavaScript 事件记录使用说明
2009/10/20 Javascript
详解JavaScript中undefined与null的区别
2014/03/29 Javascript
JsRender实用入门教程
2014/10/31 Javascript
JS+CSS实现模仿浏览器网页字符查找功能的方法
2015/02/26 Javascript
基于jQuery实现Div窗口震动特效代码-代码简单
2015/08/28 Javascript
JavaScript位置与大小(1)之正确理解和运用与尺寸大小相关的DOM属性
2015/12/26 Javascript
Javascript类型系统之undefined和null浅析
2016/07/13 Javascript
jQuery实现简洁的轮播图效果实例
2016/09/07 Javascript
浅谈js函数的多种定义方法与区别
2016/11/29 Javascript
基于JavaScript实现熔岩灯效果导航菜单
2017/01/04 Javascript
vue 过滤器filter实例详解
2018/03/14 Javascript
vue实现底部菜单功能
2018/07/24 Javascript
js实现随机点名程序
2020/09/17 Javascript
Webpack设置环境变量的一些误区详解
2019/12/19 Javascript
微信小程序自定义联系人弹窗
2020/05/26 Javascript
基于postman获取动态数据过程详解
2020/09/08 Javascript
Javascript数组及类数组相关原理详解
2020/10/29 Javascript
Python实现屏幕截图的两种方式
2018/02/05 Python
python os.path模块常用方法实例详解
2018/09/16 Python
弄懂这56个Python使用技巧(轻松掌握Python高效开发)
2019/09/18 Python
Python实现中值滤波去噪方式
2019/12/18 Python
python基于opencv实现人脸识别
2021/01/04 Python
手把手教你用Django执行原生SQL的方法
2021/02/18 Python
基于HTML5的WebGL经典3D虚拟机房漫游动画
2017/11/15 HTML / CSS
捷克领先的户外服装及配件市场零售商:ALPINE PRO
2018/01/09 全球购物
Myprotein比利时官方网站:欧洲第一运动营养品牌
2020/10/04 全球购物
大学本科毕业生的自我鉴定范文
2013/11/19 职场文书
2014年廉洁自律承诺书
2014/05/26 职场文书
车间安全生产标语
2014/06/06 职场文书
企业承诺书格式范文
2015/04/28 职场文书
2015年度物流工作总结
2015/04/30 职场文书
React自定义hook的方法
2022/06/25 Javascript