webpack入门级 - 从0开始搭建单页项目配置
前言
webpack 作为前端最知名的打包工具,能够把散落的模块打包成一个完整的应用,大多数的知名框架 cli 都是基于 webpack 来编写。这些 cli 为使用者预设好各种处理配置,使用多了就会觉得理所当然,也就不在意是内部是如何配置。如果脱离 cli 开发,可能就无从下手了。
最近在开发一些单页项目时,出于需求便开始从头搭建项目配置,本文主要分享搭建时用到的配置。
准备工作
快速生成 package.json:
npm init -y
必不可少的 webpack 和 webpack-cli:
npm i webpack webpack-cli -D
入口、出口
webpack 的配置会统一放到配置文件中去管理,在根目录下新建一个名为 webpack.config.js 的文件:
const path = require('path')
module.exports = {
entry: {
main: './src/js/main.js'
},
output: {
// filename 定义打包的文件名称
// [name] 对应entry配置中的入口文件名称(如上面的main)
// [hash] 根据文件内容生成的一段随机字符串
filename: '[name].[hash].js',
// path 定义整个打包文件夹的路径,文件夹名为 dist
path: path.join(__dirname, 'dist')
}
}
entry 配置入口,可配置多个入口。webpack 会从入口文件开始寻找相关依赖,进行解析和打包。
output 配置出口,多入口对应多出口,即入口配置多少个文件,打包出来也是对应的文件。
修改 package.json 的 script 配置:
"scripts": {
"build": "webpack --config webpack.config.js"
}
这样一个最简单的配置就完成了,通过命令行输入 npm run build 就可以实现打包。
配置项智能提示
webpack 的配置项比较繁杂,对于不熟悉的同学来说,如果在输入配置项能够提供智能提示,那开发的效率和准确性会大大提高。
默认 VSCode 并不知道 webpack 配置对象的类型,通过 import 的方式导入 webpack 模块中的 Configuration 类型后,在书写配置项就会有智能提示了。
/** @type {import('webpack').Configuration} */
module.exports = {
}

环境变量
一般开发中会分开发和生产两种环境,而 webpack 的一些配置也会随环境的不同而变化。因此环境变量是很重要的一项功能,使用 cross-env 模块可以为配置文件注入环境变量。
安装模块 cross-env:
npm i cross-env -D
修改 package.json 的命令传入变量:
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
在配置文件里,就可以这样使用传入的变量:
module.exports = {
devtool: process.env.NODE_ENV === 'production' ? 'none' : 'source-map'
}
同理,在项目的 js 内也可以使用该变量。
设置 source-map
该选项能设置不同类型的 source-map 。代码经过压缩后,一旦报错不能准确定位到具体位置,而 source-map 就像一个地图, 能够对应源代码的位置。这个选项能够帮助开发者增强调试过程,准确定位错误。
为了体验它的作用,我在源代码中故意输出一个不存在的变量,模拟线上错误:

在预览时,触发错误:

很明显错误的行数是不对应的,下面设置 devtool 让 webpack 在打包后输出 source-map 文件,用于定位错误。
module.exports = {
devtool: 'source-map'
}
再次触发错误,source-map 文件起作用准确定位到代码错误的行数。

source-map 一般只在开发环境用于调试,上线时绝对不能带有 source-map 文件,这样会暴露源代码。下面通过环境变量来正确设置 devtool 选项。
module.exports = {
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none'
}
devtool 的可选项很多,它的品质和生成速度有关联,比如只定位到某个文件,或者定位到某行某列,相应的生成速度会快或更慢。可以根据你的需求进行选择,更多可选值请查看 webpack 文档
loader 与 plugin
loader 与 plugin 是 webpack 的灵魂。如果把 webpack 比作成一个食品加工厂,那么 loader 就像很多条流水线,对食品原料进行加工处理。plugins 则是在原有的功能上,添加其他功能。
loader 基本用法
loader 配置在 module.rules 属性上。
module.exports = {
module: {
rules: []
}
}
下面来简单了解下 loader 的规则。一般常用的就是 test 和 use 两个属性:
test属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。use属性,表示进行转换时,应该使用哪个 loader。
rules: [
{
test: /\.css$/,
use: ['css-loader']
}
]
// 也可以写为
rules: [
{
test: /\.css$/,
loader: 'css-loader'
}
]
上面例子是匹配以 .css 结尾的文件,使用 css-loader 解析。
当有些 loader 可传入配置时,可以改为对象的形式:
rules: [
{
test: /\.css$/,
user: [
{
loader: 'css-loader',
options: {}
}
]
}
]
最后需要记住多个 loader 的执行是严格区分先后顺序的,从右到左,从下到上。
plugin 基本用法
plugin 配置在 plugins 属性上。
module.exports = {
plugins: []
}
一般 plugin 会暴露一个构造函数,通过 new 的方式使用。plugin 的参数则在调用函数时传入。plugin 命名一般是将按照原名转为大驼峰。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugin: [
new CleanWebpackPlugin()
]
}
生成 html 文件
没有经过任何配置的 webpack 打包出来只有 js 文件,使用插件 html-webpack-plugin 可以自定义一个 html 文件作为模版,最终 html 会被打包到 dist 中,而 js 也会被引入其中。
安装 html-webpack-plugin:
npm i html-webpack-plugin -D
配置 plugins:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: {
// 使用html模版
new HtmlWebpackPlugin({
// 配置html标题
title: 'home',
// 模版路径
template: './src/index.html',
// 压缩
minify: true
})
}
}
此时想要配置的标题生效还需要在 html 中为 title 标签插值:
<title><%= htmlWebpackPlugin.options.title %></title>
除了 title 外,还可以配置诸如 meta 标签等。
多页面
上面的使用方法,在打包后只会有一个 html。对于多页面的需求其实也很简单,有多少个页面就 new 几次 htmlWebpackPlugin。
但是需要注意一点,入口配置的 js 是会被全部引入到 html 的。如果想某个 js 对应某个 html,可以配置插件的 chunks 选项。而且需要配置 filename 属性,因为 filename 属性默认是 index.html,同名会被覆盖。
module.exports = {
entry: {
main: './src/js/main.js',
news: './src/js/news.js'
},
output: {
filename: '[name].[hash].js',
path: path.join(__dirname, 'dist')
},
plugins: {
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
minify: true,
chunks: ['main']
}),
new HtmlWebpackPlugin({
template: './src/news.html',
filename: 'news.html',
minify: true,
chunks: ['news']
}),
}
}
es6转es5
安装 babel 的相关模块:
npm i babel-loader @babel/core @babel/preset-env -D
@babel/core 是 babel 的核心模块,@babel/preset-env 内预设不同环境的语法转换插件,默认将 es6 转 es5。
根目录下创建 .babelrc 文件:
{
"presets": ["@babel/preset-env"]
}
配置 loader:
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
解析 css
安装 loader:
npm i css-loader style-loader -D
css-loader 只负责解析 css 文件,通常需要配合 style-loader 将解析的内容插入到页面,让样式生效。顺序是先解析后插入,所以 css-loader 放在最右边,第一个先执行。
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
但要注意最终打包出来的并不是css文件,而是js。它是通过创建 style 标签去插入样式。

分离css
经过上面的 css 解析,打包出来的样式会混在 js 中。某些场景下,我们希望把 css 单独打包出来,这时可以使用 mini-css-extract-plugin 分离 css。
安装 mini-css-extract-plugin:
npm i mini-css-extract-plugin -D
配置 plugins:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css'
})
]
}
配置 loader:
rules: [
{
test: /\.css$/,
use: [
// 插入到页面中
'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: {
// 为外部资源(如图像,文件等)指定自定义公共路径
publicPath: '../',
}
},
'css-loader',
]
},
]
经过上面配置后,打包后 css 就会被分离出来。

但要注意如果 css 文件不是很大的话,分离出来效果可能会适得其反,因为这样会多一次文件请求,一般来说单个 css 文件超过 200kb 再考虑分离。
css浏览器兼容前缀
安装相关依赖:
npm i postcss postcss-loader autoprefixer -D
项目根目录下新建 postcss.config.js:
module.exports = {
plugins: [
require('autoprefixer')()
]
}
package.json 新增 browserslist 配置:
{
"browserslist": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
}
最后配置 postcss-loader:
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
处理前后对比:

压缩 css
webpack 内部默认只对 js 文件进行压缩。css 的压缩可以使用 optimize-css-assets-webpack-plugin 来完成。
安装 optimize-css-assets-webpack-plugin:
npm i optimize-css-assets-webpack-plugin -D
配置 plugins:
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
plugins: [
new OptimizeCssAssetsWebpackPlugin()
]
}
压缩结果:

解析图片
项目中肯定少不了图片,对于图片资源,webpack 也有相应的 url-loader 来解析。url-loader 除了解析图片外,还可以将比较小的图片可以转为base64,减少线上对图片的请求。
安装 url-loader:
npm i url-loader -D
配置 loader:
rules: [
{
test: /\.(jpg|png|jpeg|gif)$/,
use: [{
loader: 'url-loader',
// 小于50k的图片转为base64,超出的打包到images文件夹
options: {
limit: 1024 * 50,
outputPath: './images/',
pulbicPath: './images/'
}
}]
}
]
配置完成后,只需要在入口文件内引入图片使用,webpack 就可以帮助我们把图片打包出来了。
但有时候,图片链接是直接写到 html 中,这种情况 url-loader 无法解析。不慌,使用 html-loader 能完成这项需求。
rules: [
{
test: /\.(html)$/,
use: [{
loader: 'html-loader',
options: {
// 压缩html模板空格
minimize: true,
attributes: {
// 配置需要解析的属性和标签
list: [{
tag: 'img',
attribute: 'src',
type: 'src',
}]
},
}
}]
},
]
注意这里 html-loader 只是起到解析的作用,需要配合 url-loader 或者 file-loader 去使用。也就是说解析模板的图片链接后,还是会走上面所配置的 url-loader 的流程。
还有一点,使用 html-loader 后, html-webpack-plugin 在 html 中的插值会失效。
其他类型资源解析
解析其他资源和上面差不多,不过这里用到的是 file-loader。file-loader 和 url-loader 主要是将文件上的 import / require() 引入的资源解析为url,并将该资源发送到输出目录,区别在于 url-loader 能将资源转为 base64。
安装 file-loader:
npm i file-loader -D
配置 loader:
rules: [
{
test:/\.(mp3)$/,
use: [{
loader: 'file-loader',
options: {
outputPath: './music/',
pulbicPath: './music/'
}
}]
},
{
test:/\.(mp4)$/,
use: [{
loader: 'file-loader',
options: {
outputPath: './video/',
pulbicPath: './video/'
}
}]
}
]
上面只是列举了部分,需要解析其他类型资源,参照上面的格式添加配置。
解析 html 中的其他类型资源也和上面同理,使用 html-loader 配置对象的标签和属性即可。
devServer 提高开发效率
每次想运行项目时,都需要 build 完再去预览,这样的开发效率很低。
官方为此提供了插件 webpack-dev-server,它可以本地开启一个服务器,通过访问本地服务器来预览项目,当项目文件发生变化时会热更新,无需再去手动刷新,以此提高开发效率。
安装 webpack-dev-server:
npm i webpack-dev-server -D
配置 webpack.config.js:
const path = require('path')
module.exports = {
devServer: {
contentBase: path.join(__dirname, 'dist'),
// 默认 8080
port: 8000,
compress: true,
open: true,
}
}
webpack-dev-server 用法和其他插件不同,它可以不添加到 plugins,只需将配置添加到 devServer 属性下即可。
添加启动命令 package.json:
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve"
},
}
命令行运行 npm run dev,就可以感受到飞一般的体验。
复制文件到 dist
对于一些不需要经过解析的文件,在打包后也想将它放到 dist 中,可以使用 copy-webpack-plugin。
安装 copy-webpack-plugin:
npm i copy-webpack-plugin -D
配置 plugins:
const CopyPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
// 资源路径
from: 'src/json',
// 目标文件夹
to: 'json',
}
]
}),
]
}
打包前清除旧 dist
打包后文件一般都会带有哈希值,它是根据文件的内容来生成的。由于名称不同,可能会导致 dist 残留有上一次打包的文件,如果每次都手动去清除显得不那么智能。利用 clean-webpack-plugin 可以帮助我们将上一次的 dist 清除,保证无冗余文件。
安装 clean-webpack-plugin:
npm i clean-webpack-plugin -D
配置 plugins:
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
插件会默认清除 output.path 的文件夹。
自定义压缩选项
webpack 从 v4.26.0 开始内置的压缩插件变为 terser-webpack-plugin。如果没有其他需求,自定义压缩插件也尽量保持与官方的一致。
安装 terser-webpack-plugin:
npm i terser-webpack-plugin -D
配置 plugins:
const TerserPlugin = reuqire('terser-webpack-plugin')
module.exports = {
optimization: {
// 默认为 true
minimize: true,
minimizer: [
new TerserPlugin()
]
},
}
插件压缩配置可以查阅 terser-webpack-plugin 文档,在调用时自定义选项。
像上面的 css 压缩插件也可以添加到 optimization.minimizer。与配置到 plugins 的区别是,配置到 plugins 的插件在任何情况都会去执行,而配置到 minimizer 中,只有在 minimize 属性开启时才会工作。
这样做的目的便于通过 minimize 属性来统一控制压缩。
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = reuqire('terser-webpack-plugin')
module.exports = {
optimization: {
// 默认为 true
minimize: true,
minimizer: [
new TerserPlugin(),
new OptimizeCssAssetsWebpackPlugin()
]
},
}
注意如果你提供 minimizer 选项而没有使用 js 压缩插件,即使 webpack 内置 js 压缩,打包出来的 js 也不会被压缩。因为 webpack 压缩配置会被 minimizer 覆盖。
排查错误的建议
在使用 webpack 的过程中,这玩意偶尔会有些奇奇怪怪的报错。
下面是我遇到的一些错误以及解决方法(仅供参考并不是万能法则):
- 一些 loader 和 plugin 在使用时,会依赖 webpack 的版本。如果使用过程发生错误,检查是否有版本不兼容的问题,可以尝试降一个版本。
- 重新安装依赖,有可能下载过程中,一些依赖会没装上。
- 查看使用文档,不同版本所传入的选项属性可能会不一样(被坑过) 。
还有注意控制台的提示,一般根据错误提示都能猜出大概是什么问题。
依赖版本和完整配置
项目结构:

依赖版本:
{
"@babel/core": "^7.12.3",
"@babel/preset-env": "^7.12.1",
"autoprefixer": "^10.0.1",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.3.0",
"cross-env": "^7.0.2",
"css-loader": "^5.0.0",
"file-loader": "^6.2.0",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss": "^8.1.6",
"postcss-loader": "^4.0.4",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^4.44.2",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
}
webpack.config.js:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
/** @type {import('webpack').Configuration} */
module.exports = {
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none',
entry: {
main: './src/js/main.js'
},
output: {
filename: '[name].[hash].js',
path: path.join(__dirname, 'dist')
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 8000,
compress: true,
open: true,
},
plugins: [
// 清除上一次的打包内容
new CleanWebpackPlugin(),
// 复制文件到dist
new CopyPlugin({
patterns: [
{
from: 'src/music',
to: 'music',
}
]
}),
// 使用html模版
new HtmlWebpackPlugin({
template: './src/index.html',
minify: true
}),
// 分离css
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css'
}),
// 压缩css
new OptimizeCssAssetsWebpackPlugin()
],
module: {
rules: [
// 解析js(es6转es5)
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
}
},
'css-loader',
'postcss-loader'
]
},
{
test: /\.(html)$/,
use: [{
// 主要为了解析html中的img图片路径 需要配合url-loader或file-loader使用
loader: 'html-loader',
options: {
attributes: {
list: [{
tag: 'img',
attribute: 'src',
type: 'src',
},{
tag: 'source',
attribute: 'src',
type: 'src',
}]
},
minimize: true
}
}]
},
// 解析图片
{
test: /\.(jpg|png|gif|svg)$/,
use: [{
loader: 'url-loader',
// 小于50k的图片转为base64,超出的打包到images文件夹
options: {
limit: 1024 * 50,
outputPath: './images/',
pulbicPath: './images/'
}
}]
},
// 解析其他类型文件(如字体)
{
test: /\.(eot|ttf)$/,
use: ['file-loader']
},
{
test: /\.(mp3)$/,
use: [{
loader: 'file-loader',
options: {
outputPath: './music/',
pulbicPath: './music/'
}
}]
}
]
},
}
最后
由于单页项目简单,配置项比较朴实无华,本文主要是些基础配置。不过套路都差不多,根据项目的需求去选择 loader 和 plugin。更多的还是要了解这些插件的作用和使用方法,以及其他常用的插件。
webpack入门级 - 从0开始搭建单页项目配置的更多相关文章
- Vue之vue自动化工具快速搭建单页项目目录
1 生成项目目录 使用vue自动化工具可以快速搭建单页应用项目目录. 该工具为现代化的前端开发工作流提供了开箱即用的构建配置.只需几分钟即可创建并启动一个带热重载.保存时静态检查以及可用于生产环境的构 ...
- [转]Java Web笔记:搭建环境和项目配置(MyEclipse 2014 + Maven + Tomcat)
来源:http://www.jianshu.com/p/56caa738506a 0. 绪言 Java Web开发中,除了基础知识外,开发环境搭建,也是一项基本功.开发环境包括了IDE.项目管理.项目 ...
- 前后端分离最佳实现,使用Nuxt.js快速搭建单页SSR应用
通常我们搭建ssr应用需要自己选择多个组件集成到一起 webpack babel loaders router server-render 各种入口配置等 如果是基于vue+vuex+vue-rout ...
- 跟我一起学习vue2(使用命令行搭建单页应用)[二]
第一步:运行git命令,全局安装 vue-cli $ cnpm install --global vue-cli 第二步: 创建一个基于 webpack 模板的新项目 $ vue init webpa ...
- vue-router+webpack线上部署时单页项目路由,刷新页面出现404问题
使用vue项目,线上部署的时候,访问首页以及通过路由打开二级页面没有问题,但是一刷新就出现404现象 因为刷新页面时访问的资源在服务端找不到,因为vue-router设置的路由不是真实存在的路径. 解 ...
- Vue Cli 3 搭建单页应用项目刷新 404 问题 解决方案(以Apache为例)
vue 项目 版本 Vue Cli 3.3 官方文档 https://router.vuejs.org/zh/guide/essentials/history-mode.html 因为本项目部署在 A ...
- 搭建EF6.0+MVC4搭建框架——之路由配置
为了适应项目需求,需要将前后台的控制器和视图等文件分开,便于修改和维护: 方案一:在原有的Controller下新增Admins文件夹用于放置后台控制器文件: 控制器文件目录如下图: 视图文件目录:
- 基于【CentOS-7+ Ambari 2.7.0 + HDP 3.0】搭建HAWQ数据仓库——安装配置NTP服务,保证集群时间保持同步
一.所有节点上使用yum安装配置NTP服务yum install ntp -y 二.选定一台节点作为NTP server, 192.168.58.11修改/etc/ntp.conf vim /etc/ ...
- Nginx部署前后端分离的单页应用配置
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #erro ...
随机推荐
- springboot同一项目部署多实例
添加 -Dserver.port=xxxx 将配置文件放在nacos注册中心时,要记得在启动第二个实例记得把原来端口注释掉,如果配置文件在本地就不必注释掉了
- [转载]Windows环境下 Hadoop Error: JAVA_HOME is incorrectly set. 问题
最近尝试在windows开发MR程序并且提交Job,在解压缩好hadoop,配置好环境变量后, 打开cmd 输入hadoop version 的时候出现以下错误: Error: JAVA_HOME i ...
- yii2.0 实现城市联动效果
<script type="text/javascript"> function getcitytext(){ citytext = ''; $(".city ...
- canvas 元素覆盖&穿透问题
给网站添加canvas动态背景.完后发现有a标签无法点击,想到是canvas覆盖了(但有些是可以的).网上查找,有解决穿透的问题,但canvas的鼠标事件会无效.后发现是定位问题. canvas样式 ...
- Java基础教程——封装
面向对象的三大特征 封装:encapsulation 继承:inheritance 多态:polymorphism 封装 类是一个最基本的封装 封装的好处: 数据安全:保证数据安全 方便调用:提供清晰 ...
- 编程语言输出“ Hello World ”,你真的都会了吗?
Hello World 中文意思是『你好,世界』.因为<The C Programming Language>中使用它做为第一个演示程序,非常著名,所以后来的程序员在学习编程或进行设备调试 ...
- arthas监控elasticsearch(7.x)
arthas介绍 arthas是Alibaba推出的java诊断工具 官方文档 准备 准备docker环境 name port centos_arthas 3658:3658 docker run - ...
- CentOS SSH安全和配置无密码登录
CentOS ssh默认监听端口 22端口,允许root用户ssh登录.server投入生产后很有必要更改默认ssh监听端口和禁止root登录. 步骤1:确认安装有ssh包 [appuser@su17 ...
- linux系统下oracle表空间占用情况
1.我们先查询表空间的占用情况,使用sql如下: select upper(f.tablespace_name) "表空间名", d.tot_grootte_mb "表空 ...
- 题解 洛谷 P3396 【哈希冲突】(根号分治)
根号分治 前言 本题是一道讲解根号分治思想的论文题(然鹅我并没有找到论文),正 如论文中所说,根号算法--不仅是分块,根号分治利用的思想和分块像 似却又不同,某一篇洛谷日报中说过,分块算法实质上是一种 ...