在京东,我们这样配置webpack(2万字长文,建议收藏)

文章目录

  • 本篇的目的
  • 1、webpack基础
    • 1.1、webpack开发环境搭建
    • 1.2、webpack配置核心
    • 1.3、开发及生产环境分离
    • 1.4、处理静态资源
  • 2、webpack热门插件
    • 2.1、 HtmlWebpackPlugin
    • 2.2 、clean-webpack-plugin
  • 3、提升开发效率的利器
    • 3.1、WebpackDevServer
    • 3.2、本地mock数据,解决跨域
    • 3.3 、Babel处理ES6
    • 3.4 、css文件的处理
  • 4、webpack性能优化---优化开发体验
    • 4.1、优化loader配置
    • 4.2、优化resolve.modules配置
    • 4.3、优化resolve.alias配置
    • 4.4、优化resolve.extensions配置
    • 4.5、优化文件监听的性能
    • 4.6、HMR 热模块替换
    • 4.7、sourceMap
    • 4.8、HardSourceWebpackPlugin
    • 4.9、调试工具
  • 5、webpack性能优化---优化输出质量
    • 5.1、借助MiniCssExtractPlugin完成抽离css
    • 5.2、压缩css
    • 5.3、压缩HTML
    • 5.4、tree Shaking
    • 5.5、代码分割 code Splitting
    • 5.6、图片压缩
    • 5.7、压缩打包
    • 5.8、雪碧图
    • 5.9、px转rem,适配不同屏幕大小
  • 6、开发环境的配置代码
  • 7、生产环境的配置代码
  • 8、项目的github地址

本篇的目的

  • 本篇教程是手把手教你从0到1新建webpack工程化项目,按照下文的步骤最终会创建出一个高效率,高质量的通用webpack脚手架,如果对下文代码有疑问,为了自己,请在编辑器里手敲代码,并运行试试。希望您不虚此行,现在起航…

1、webpack基础

1.1、webpack开发环境搭建

Webpack是模块打包工具,是工程化,自动化思想在前端开发中的体现,
1.1.1、初始化一个项目
项目安装 (推荐)新建一个文件夹并进入,init是初始化命令,-y参数可以免去中间的配置过程,直接一步初始化成功。

npm init -y

1.1.2、安装webpack,

  • 参数 iinstall的简写,
    --D--save-dev的简写,仅在开发环境才用的包,会被注册在package.json里的devDependencies
    --S--save的简写,会打包到生成的包里面,是发布内容的一部分,会被注册在package.json里的dependencies
npm i -D webpack webpack-cli

1.1.3、如果想通过命令执行npm run dev来启动webpack,需要在package.json里加入下面代码。”scripts“的对象里可以配置一些打包命令。dev是命令的名称,webpack是打包命令具体执行的语句,--config是指定配置的文件,紧跟在后面的是配置文件名。详见下文章节 >> 1.3、开发及生产环境分离

"scripts": {
     
	"dev": "webpack --config ./webpack.config.dev.js",
},

1.1.4、新建src文件夹,目录结构如下图。 新建src/index.js ,这是入口文件,写业务代码的地方
在京东,我们这样配置webpack(2万字长文,建议收藏)_第1张图片

// index.js
console.log("hello webpack")

1.1.5、执行打包命令npm run dev后,成功后会发现在目录下多了一个dist文件夹,目录如下图,里面有个main_xxxxxx.js,能打包成功是因为有下文1.1.6的配置
在京东,我们这样配置webpack(2万字长文,建议收藏)_第2张图片
1.1.6、在根目录下新建文件webpack.config.dev.js,输入下面的默认配置,path是必备的配置项目绝对路径的插件,__dirname指当前文件的绝对路径

const path = require("path");
module.exports = {
     
	// 必填 webpack执行打包的唯一入口
	entry: {
     
    	main: [path.resolve(__dirname, './src/index.js')],
  	},
	output: {
     
		// 将所有依赖的模块合并输出到main_xxxxxx.js,xxxxxx为随机生成的6位hash码
		//当内容有改变时,hash会变化,防止缓存原因导致修改不更新
		filename: 'js/[name]_[hash:6].js',
		// 输出文件的存放路径, 必须是绝对路径
		path: path.resolve(__dirname, "./dist")
	}
}

1.2、webpack配置核心

1.2.1,-entry
指定webpack打包入口,webpack执行构建的第一步将从entry开始

//多入口 entry是个对象,最终会在输出的文件夹里生成两个文件,base.js和main.js
entry:{
     
	// 解释在下文章节 >> 3.3 、Babel处理ES6
    base: ['core-js/stable', 'regenerator-runtime/runtime'],
    main: [path.resolve(__dirname, './src/index.js')],
}

1.2.2, -output
打包转换后的文件输出到磁盘位置:

// 多入口的处理
output: {
     
	filename: "[name][hash:6].js", // 利用占位符,文件名称不重复
	path: path.resolve(__dirname, "dist") // 输出文件到磁盘的目录,必须是绝对路径
}

1.2.3,-mode
Mode用来指定当前的打包环境

  • production 生产模式
  • development 开发模式

如果没有设置,webpack会将mode的默认值设置为production

1.2.4, -loader
模块解析,模块转换器
webpack是模块打包工具,而模块不仅仅是js,还可以是css,图片或者其他格式
但是webpack默认只处理jsjson,其他模块就需要用loader

1.2.5, -module
模块配置,在webpack里一切皆模块,用来配置需要的匹配规则及使用哪种loader转换器

module:{
     
	rules:[
		test:/\.xxx$/,//指定匹配规则
		use:{
     
			loader:'xxx-load'//指定使用的loader
		}
	]
}

1.3、开发及生产环境分离

在根目录下新建2个文件,分别是webpack.config.dev.js和webpack.config.pro.js.

  • webpack.config.dev.js是开发环境的配置,具体代码查看下文章节 >> 6、开发环境的配置代码
  • webpack.config.pro.js是线上环境的配置,具体代码查看下文章节 >> 7、生产环境的配置代码
  • 会在下文的每段代码的注释中,标注本段代码属于哪种环境的配置

修改package.json文件里的"scripts"指令

  • dev 是在开发环境下,生成一个dist目录的文件夹
  • server 是在开发环境下,在浏览器中打开一个热更新的页面
  • build 是在生产环境下,生成一个build目录的文件夹
  • zip是在生产环境下,打一个zip压缩包
"scripts": {
     
    "dev": "webpack --config ./webpack.config.dev.js",
    "server": "webpack-dev-server --config ./webpack.config.dev.js --env.debug",
    "build": "webpack --config ./webpack.config.pro.js",
    "zip": "webpack --config ./webpack.config.pro.js --env.zip"
  },

1.4、处理静态资源

loader:file-loader
原理是把打包入口中识别出的资源模块,移动到输出目录,并提供输出目录的地址用于替换资源中的原始路径
场景:用file-loader处理,txt,svg,csv,excel,图片资源等等

npm i -D file-loader

//代码放入文件webpack.config.dev.js webpack.config.pro.js
module: {
     
	rules: [
		{
     
			test: /\.(png|jpe?g|gif)$/,
			//use使用一个loader可以用对象,字符串,两个loader需要用数组
			use:{
     
				loader: "file-loader",
				//options额外的配置,比如资源名称
				options: {
     
					//placeholder 占位符 [name]老资源模块的名称 [ext]老资源模块的后缀
					name: "[name]_[hash].[ext]",
					//打包后存放的位置
					outputPath: "images/"
				}
			}
		},
        {
     
          test: /\.(eot|ttf|woff|woff2|svg)$/,
          use: "file-loader",
        }
	]
}

url-loader是file-loader的加强版本 npm i -D url-loader

url-loader内部使用了file-loader,但是遇到小于10000,即8kb的png、jpg、jpeg、gif文件会转换成base64格式的字符串,并打包到css和js里。对于小体积的图片比较适合

module: {
     
	rules: [
		{
     
			test: /\.(png|jpe?g|gif)$/,
			use: {
     
				loader: "url-loader",
				options: {
     
					name: "[name]_[hash].[ext]",
					outputPath: "images/",
					//小于10000,即8kb,才转换成base64
					limit:10000
				}
			}
		}
	]
}

2、webpack热门插件

扩展插件,在webpack打包流程中的特定时机注入扩展逻辑,来改变打包结果,或者做你想要的事情。

2.1、 HtmlWebpackPlugin

htmlwebpackplugin会在打包结束后,自动生成一个html文件,并把打包生成的js模块引入到该html中

npm i -D html-webpack-plugin

配置,新建src/index.html文件,title引用了htmlWebpackPlugin配置的标题,方便在webpack的配置中直接修改标题

<title><%= htmlWebpackPlugin.options.title %></title>
//代码放入文件webpack.config.dev.js webpack.config.pro.js
module.exports = {
     
	plugins: [
		new htmlWebpackPlugin({
     
			title: "My App", //标题
			filename: "index.html",//输出的文件名,默认是index.html
			template: "./src/index.html"//模板文件路径
		})
	]
}

2.2 、clean-webpack-plugin

清理上次打包生成的dist无用代码

npm i -D clean-webpack-plugin

//代码放入文件webpack.config.dev.js webpack.config.pro.js
const {
      CleanWebpackPlugin } = require("clean-webpack-plugin")
plugins:[
	new CleanWebpackPlugin({
     
		//打包之前清理一次,删掉上次打包生成的dist文件夹
		cleanOnceBeforeBuildPatterns: [
			path.resolve(__dirname,'./dist')
		]
	})
]

3、提升开发效率的利器

3.1、WebpackDevServer

每次改完代码都需要重新打包一次,打开浏览器刷新,很麻烦。使用webpack-dev-server来实时刷新页面,代码刚改完,页面就刷新完成了。

npm i -D webpack-dev-server

在package.json文件中的指令为

"scripts": {
     
	"server": "webpack-dev-server --config ./webpack.config.dev.js --env.debug",
}

在webpack.config.dev.js配置

//代码放入文件webpack.config.dev.js
module.exports = {
     
	devServer: {
     
		contentBase: "./dist",//指定被访问html页面所在的目录的
		open:true,// 指运行npm run server指令后,自动在浏览器里打开一个页面
		port:8081 // 指定打开的页面的端口为8081,也可以指定其他端口
	}
}

3.2、本地mock数据,解决跨域

前后端分离,wepack搞定开发期mock测试数据,启动一个本地服务,mock多个接口,首先安装expressfs

npm i express fs -D

  • express是基于nodejs平台的一个极简web开发框架。
  • fs模块用于对系统文件及目录进行读写操作。
    在src文件夹里创建一个server文件夹,server文件夹下再新建两个文件,分别叫data1.js和data2.js,用来放入测试数据。可以建多个文件,每个文件里也可以有多个接口。代码如下
//data1.js
module.exports = function(app){
     
	app.get('/api',function(req,res){
     
		res.jsonp({
     name:'lisi',age:11})
	})
	app.get('/api/about',function(req,res){
     
		res.jsonp({
     name:'zhangsan',age:40})
	})
}
//data2.js
module.exports = function(app){
     
	app.get('/api/home',function(req,res){
     
		res.jsonp({
     name:'李四',age:11})
	})
	app.get('/api/list',function(req,res){
     
		res.jsonp({
     name:'张三',age:40})
	})
}

在src目录下新建server执行文件,用来自动加载刚才新建的测试数据,这里的代码无需修改,即可自动加载全部的接口及数据。

// server.js
const express = require('express') //nodejs的web框架
const app = express() //实例化框架
const fs = require('fs') //操作文件的模块
const dir = './server' //接口路径

//readdirSync 该方法返回一个包含指定目录下所有文件名称的数组对象
const list = fs.readdirSync(dir).map((v) => {
     
  require(dir + '/' + v)(app) //拼接出文件路径后,导入执行,同时传入app
})

app.listen('9092') //监听端口

在命令行里进入src文件夹后,再运行指令node server.js,出现跨域报错时,通过修改webpack.config.js设置服务器代理的方法解决

devServer:{
     
	...
	proxy: {
     
		//凡是api这个接口,都会被代理到target的路径上
		'/api': {
     
			target: 'http://localhost:9092',
			//发送请求头host会被设置为target
            changeOrigin:true
		}
	}
}

在业务代码里,就能通过上面的数据接口,读取到返回的jsonp数据了。

3.3 、Babel处理ES6

Babel是javascript编译器,能将ES6代码转换成ES5代码,下面安装

npm i babel-loader @babel/preset-env -D
npm i core-js regenerator-runtime -S

  1. babel-loader是webpack与babel的通信桥梁,@babel/preset-env把es6转成es5
  2. @babel/preset-env里包含了es6,7,8转es5的规则
  3. 默认的Babel只支持一些基础的特性转换,高级的转换需要对象的转换需要core-js/stableregenerator-runtime/runtime
  4. 关于core-js@3的详细介绍请看https://www.cnblogs.com/sefaultment/p/11631314.html
//代码放入文件webpack.config.dev.js webpack.config.pro.js
entry:{
     
	//导入core-js@3的转译代码,实现了完全无污染的API转译,非常有潜力
	base:['core-js/stable','regenerator-runtime/runtime'],
	main:[path.resolve(__dirname, './src/index.js')]
	//打包成功后,会生成两个文件,base.js和main.js
},
output:{
     
	filename: "js/[name]_[hash:6].js",// 多模块导出,[name]指entry中的base和main,[hash:6]指生成6位hash值
	path:path.resolve(__dirname, './dist')
},
module:{
     
	rules: [
		{
     
			test:/\.jsx?$/,//适配js和jsx
			use: {
     
				loader: 'babel-loader'
			}
		}
	]
}
  • 在package.json里配置browserslist,
  • babel会根据所需适配的浏览器目标进行对应的转换,浏览器目标可通过browserslist属性设置
  • 具体配置参考https://www.npmjs.com/package/browserslist。
"browserslist": [
	"> 1%", //全球超过1%使用的浏览器
	"last 2 versions" //所有的浏览器兼容到最近的2个版本
]

按需加载,减少冗余,根目录下新建.babelrc文件,

//.babelrc
{
     
	"presets": [
		[
			"@babel/preset-env",
			{
     
				"corejs": 3,//新版本需要指定核心库版本
				"useBuiltIns": "usage" //按需注入
			}
		]
	]
}

3.4 、css文件的处理

  • css-loader分析css模块之间的关系,并合成一个css
  • style-loader会吧css-loader生成的内容,以style挂载到页面的head部分
  • sass-loader 把sass语法转换成css
  • postcss-loader 样式自动添加前缀

npm i -D style-loader css-loader sass-loader node-sass postcss-loader postcss-preset-env

//代码放入文件webpack.config.dev.js webpack.config.pro.js
module: {
     
	rules: [
		{
     
			test: /\.(css|scss)$/,
			//loader是有顺序的,从后往前
			use:[
				'style-loader',//在页面插入css样式
				'css-loader',//抽取css样式
				'postcss-loader',//样式前缀自动补全
				'sass-loader' //sass当做css技术栈
			]
		}		
	]
}

根目录下新建postcss.config.js

const postcssPresetEnv = require('postcss-preset-env')
module.exports = {
     
	plugins: [
		//使用postcss为样式自动补齐浏览器前缀,浏览器的版本限制在package.json里的browserslist配置
		postcssPresetEnv(),
	]
}

4、webpack性能优化—优化开发体验

4.1、优化loader配置

  • 推荐include,缩小loader的处理范围,下面的配置只在src文件夹下使用loader转译器
cinclude:path.resolve(__dirname, "./src")

4.2、优化resolve.modules配置

resolve.modules用于配置webpack去哪些目录下寻找第三方模块,默认是[‘node_modules’],我们的第三方模块都安装在了项目根目录下,就可以直接指明这个路径

//代码放入文件webpack.config.dev.js webpack.config.pro.js
module.exports = {
     
	resolve:{
     
		modules: [path.resolve(__dirname, "./node_modules")]
	}
}

4.3、优化resolve.alias配置

resolve.alias配置通过别名来将原导入路径映射成一个新的导入路径

//代码放入文件webpack.config.dev.js webpack.config.pro.js
resolve:{
     
	alias: {
     
		react: path.resolve(
			__dirname,
			"./node_modules/react/umd/react.production.min.js"
	 	),
		"react-dom": path.resolve(
			__dirname,
			"./node_modules/react-dom/umd/react-dom.production.min.js"
	 	)
	}
}

4.4、优化resolve.extensions配置

resolve.extensions在导入语句没带文件后缀时,webpack会自动带上后缀,去尝试查找文件是否存在,但是在查找的时候,会耗费一定的打包时间,默认值:

extensions:['.js','.json','.jsx','.ts']
  • 后缀尝试列表尽量的小
  • 导入语句尽量带上后缀

4.5、优化文件监听的性能

//代码放入文件webpack.config.dev.js
module.export = {
     
	watchOptions: {
     
		//不监听的node_modules目录下的文件
		ignored: /node_nodules/,
	}
}

采用这种方法优化后,Webpack消耗的内存和CPU将会大大减少

4.6、HMR 热模块替换

自动及时更新代码,自动重编译,只适合开发模式。

//代码放入文件webpack.config.dev.js
devServer: {
     
	hot:true,
	hotOnly:true,//即便HMR不生效,浏览器也不自动刷新,就开启hotOnly
}
plugins:[
	new webpack.HotModuleReplacementPlugin()
]

4.7、sourceMap

源代码与打包后的代码的映射关系,通过sourceMap定位到源代码关闭的话可以在配置文件里devtool: “none”

//代码放入文件webpack.config.dev.js
devtool:"cheap-module-eval-source-map",// 开发环境配置
//线上环境不推荐开启

打包后预览页面,还能看到我们的源代码
在京东,我们这样配置webpack(2万字长文,建议收藏)_第3张图片

4.8、HardSourceWebpackPlugin

项目中的第三方库,基本不更新,打包的时候分离出来,提升打包速度。

//代码放入文件webpack.config.dev.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
plugins = [
	new HardSourceWebpackPlugin()
]

4.9、调试工具

会在页面中添加一个按钮,点击会出现一个调试器,输出console信息,及各种报错信息

npm i -D vconsole-webpack-plugin

在文件webpack.config.dev.js中加入如下代码

//代码放入文件webpack.config.dev.js
const vConsolePlugin = require("vconsole-webpack-plugin")
plugins:[
	new vConsolePlugin({
     
		enable: true,
		filter: ['base']
	})
]

5、webpack性能优化—优化输出质量

5.1、借助MiniCssExtractPlugin完成抽离css

单独生成css,可以和js并行下载,提高页面加载效率,与HMR热模块替换冲突,所以只在mode为production模式下打包使用

npm i mini-css-extract-plugin -D

//代码放入文件webpack.config.pro.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module:{
     
	reles: [
		{
     
			test: /\.scss$/,
			use: [
				MiniCssExtractPlugin.loader,
				"css-loader",
				"postcss-loader",
				"sass-loader"
			]
		}
	]
},
plugins: [
	new MiniCssExtractPlugin({
     
		filename: "css/[name]_[contenthash:6].css",
		chunkFilename: "[id].css"
	})
]

5.2、压缩css

  • 借助optimize-css-assets-webpack-plugin
  • 借助cssnano,详细配置参数请查看https://cssnano.co/guides/optimisations

npm i -D cssnano optimize-css-assets-webpack-plugin

//代码放入文件webpack.config.pro.js
new OptimizeCSSAssetsPlugin({
     
	cssProcessor: require("cssnano"),//引入cssnano配置压缩选项
	cssProcessorOptions: {
     
		discardComments: {
      removeAll: true }
	}
})

5.3、压缩HTML

  • 借助html-webpack-plugin
//代码放入文件webpack.config.pro.js
new htmlWebpackPlugin({
     
	title: "My App", //标题
	filename: "index.html",//输出的文件名,默认是index.html
	template: "./src/index.html"//模板文件路径
	minify: {
     
		//压缩HTML文件
		removeomments: true,//移除HTML中的注释
		collapseWhitespace: true, // 删除空白符和换行符
		minifyCss: true //压缩内联css
	}
})

5.4、tree Shaking

“摇树”,清除无用css,js(Dead Code)

npm i glob-all purify-css purifycss-webpack -D

//代码放入文件webpack.config.pro.js
const PurifyCss = require("purifycss-webpack")
const glob = require("glob-all")
plugins:[
	// 清除无用 css
	new PurifyCss({
     
		paths: glob.sync([
			// 要做 CSS Tree Shaking 的路径文件
			path.resolve(__dirname, './src/*.html'),// 请注意,我们同样需要对 html 文件进行 tree shaking
			path.resolve(__dirname, './src/*.js')
		])
	})
]

只支持import方式引入,不支持commonjs的方式引入

//代码放入文件webpack.config.pro.js
optimization:{
     
	usedExports: true //哪些导出的模块被使用了,再做打包
}
//package.json
”sideEffects“:false,//正常对所有模块进行tree shaking
//或者在数组里排除不需要tree shaking的模块,应采用数组排除,不然css抽取为单独文件将失效
// 仅生产模式有效,需要配合usedExports
”sideEffects“:["*.css","*.scss","core-js","regenerator-runtime","*.jpg","*.png","*.gif",'react','react-dom']

5.5、代码分割 code Splitting

打包后,如果只生成一个main.js的话,代码体积很大,不利于下载,没有合理利用浏览器资源。使用代码分割后,根据配置会生成几个文件,可以同时下载,充分利用了浏览器的资源。

//代码放入文件webpack.config.pro.js
optimization:{
     
	splitChunks: {
     
		chunks: "all", // 所有的 chunks 代码公共的部分分离出来成为一个单独的文件
		cacheGroups:{
     
			//缓存组
			react: {
     
				test: /react|react-dom/,
				name: 'react',
				minChunks: 1,
			}
		}
	}
}

分割前输出的文件结构见上面章节 >> 1.1.5
分割后输出的文件结构如下,多分出来了react_xxxxxx.js及其他
在京东,我们这样配置webpack(2万字长文,建议收藏)_第4张图片

5.6、图片压缩

基于 tinypng.com 封装的一个支持nodejs、命令行 和webpack的图片压缩工具。首先需要去
https://tinypng.com/developers这个网站申请key,申请方法为:获取并输入全名和邮箱,点击按钮就会生成API key,拷贝到项目配置中。
在这里插入图片描述

npm i -D tinypng-webpack-plugin

在文件webpack.config.pro.js中加入如下代码

//代码放入文件webpack.config.pro.js
const tinyPngWebpackPlugin = require("tinypng-webpack-plugin")
plugins:[
	new tinyPngWebpackPlugin({
     
		key: [
	        '----------这里填入申请到的API key--------',
	        '----------这里填入申请到的API key--------',
	        '----------这里填入申请到的API key--------',
	        '----------这里填入申请到的API key--------',
	        '----------这里填入申请到的API key--------',
		],
		ext: ['png','jpeg','jpg']
	})
]

5.7、压缩打包

把最后生成的文件夹打包成zip压缩包

npm i -D filemanager-webpack-plugin

在webpack.config.pro.js文件中加入如下代码

//代码放入文件webpack.config.pro.js
const FilemanagerWebpackPlugin = require("filemanager-webpack-plugin")
plugins:[
	new FilemanagerWebpackPlugin({
     
		onEnd: {
     
			copy: [//拷贝代码
				{
     //拷贝代码的源,即我们打包生成的文件夹
					source:path.resolve(__dirname, './build'),
					//拷贝到一个临时的文件夹里
					destination: path.resolve(__dirname,'./tmp_for_zip/dist')
				}
			],
			archive: [
				{
     //生成压缩包的源,即是我们刚刚拷贝的文件夹
					source:path.resolve(__dirname,'./tmp_for_zip'),
					//压缩后的包名及格式
					destination: path.resolve(__dirname,'./dist.zip')
				}
			],
			//删除刚刚新建的临时文件夹
			delete: [path.resolve(__dirname,'./tmp_for_zip')]
		}
	})
]

5.8、雪碧图

  • 把小图合成一张大图的功能,sprite文件夹下的每个子文件夹都会合成一个雪碧图,有多少个子文件夹就有多少张雪碧图,有利于控制雪碧图的大小和数量。不在sprite文件夹下的图片则不合成雪碧图
  • 文件结构如下,a.png和b.png会被合成sprite.src-img1.png
  • c.png和d.png会被合成sprite.src-img2.png
└─src
    │     
    └─sprite
        │  
        ├─img1
        │      a.png
        │      b.png
        │      
        └─img2
                c.png
                d.png

npm i -D postcss-sprites postcss

在文件postcss.config.js中,添加如下代码

const path = require('path')
const sprites = require('postcss-sprites')
const postcss = require('postcss')

module.exports = {
     
  plugins: [
    sprites({
     
      filterBy: ({
      path: imgPath }) => {
     
        //非sprite文件夹下的图片不合并
        const [first, second] = path.dirname(imgPath).split(path.sep).reverse()
        return first == 'sprite' || second === 'sprite'
          ? Promise.resolve()
          : Promise.reject()
      },
      groupBy: ({
      path: imgPath }) => {
     
        //以sprite文件夹下的子目录作为分组,子目录下的图片和合并成一张雪碧图
        const [first, second, third] = path
          .dirname(imgPath)
          .split(path.sep)
          .reverse()
        const {
      name } = path.parse(imgPath)
        if (first === 'sprite') {
     
          return Promise.resolve(`${
       second}-${
       name}`)
        } else if (second === 'sprite') {
     
          return Promise.resolve(`${
       third}-${
       first}`)
        } else {
     
          return Promise.reject()
        }
      },
      hooks: {
     
        onUpdateRule: (
          rule,
          token,
          {
      coords, ratio, spriteWidth, spriteHeight, spriteUrl }
        ) => {
     
          //start:修改自postcss-sprites/lib/core中的updateRule方法
          const posX = -Math.abs(coords.x / ratio)
          const posY = -Math.abs(coords.y / ratio)
          const sizeX = spriteWidth / ratio
          const sizeY = spriteHeight / ratio
          token
            .cloneAfter({
     
              type: 'decl',
              prop: 'background-image',
              value: `url(./${
       spriteUrl})`,
            })
            .cloneAfter({
     
              prop: 'background-position',
              value: `${
       posX}px ${
       posY}px`,
            })
            .cloneAfter({
     
              prop: 'background-size',
              value: `${
       sizeX}px ${
       sizeY}px`,
            })
          //end:修改自postcss-sprites/lib/core中的updateRule方法

          //start:若原始样式中没有设置width或height,则根据图片大小自动添加width或height属性
          const dimensions = ['width', 'height']
          rule.some(({
      prop }) => {
     
            dimensions.some((targetProp, idx) => {
     
              if (prop === targetProp) {
     
                dimensions.splice(idx, 1)
              }
            })
          })
          dimensions.forEach((prop) => {
     
            const val = coords[prop]
            rule.insertAfter(
              rule.last,
              postcss.decl({
     
                prop: prop,
                value: `${
       val}px`,
              })
            )
          })
          //end:若原始样式中没有设置width或height,则根据图片大小自动添加width或height属性
        },
      },
      relativeTo: 'rule',
      spritePath: './src/img',
      spritesmith: {
     
        padding: 4,
      },
      stylesheetPath: './src/css',
    }),
  ],
}

5.9、px转rem,适配不同屏幕大小

通过上面的代码把css里的px转换成了rem,在index.html的head中加入下面的代码,即可兼容不同屏幕

<script>
      document.documentElement.style.fontSize =
        (document.documentElement.clientWidth || document.body.clientWidth) /
          7.5 +
        'px'
      window.addEventListener('resize', () => {
     
        document.documentElement.style.fontSize =
          (docuemnt.documentElement.clientWidth || document.body.clientWidth) /
            7.5 +
          'px'
      })
    </script>

需文件postcss.config.js中的如下代码配合

npm i -D postcss-px2rem

const px2rem = require('postcss-px2rem')
module.exports = {
     
  plugins: [
    px2rem({
     
      remUnit: 100,
    }),
  ]

6、开发环境的配置代码

在webpack.config.dev.js文件里放入如下代码

const path = require('path')
const webpack = require('webpack')
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const vConsolePlugin = require('vconsole-webpack-plugin')
const htmlWebpackPlugin = require('html-webpack-plugin')
const {
      CleanWebpackPlugin } = require('clean-webpack-plugin')

const devConfig = (debug) => ({
     
  entry: {
     
    base: ['core-js/stable', 'regenerator-runtime/runtime'],
    main: [path.resolve(__dirname, './src/index.js')],
  },
  output: {
     
    filename: 'js/[name]_[hash:6].js',
    path: path.resolve(__dirname, './dist'),
  },
  // mode:"development",
  mode: 'development',
  module: {
     
    rules: [
      {
     
        test: /\.(css|scss)$/,
        include: path.resolve(__dirname, './src'),
        //loader是有顺序的,从后往前
        use: [
          'style-loader', //在页面插入css样式
          'css-loader', // 抽取css样式
          'postcss-loader', // 样式前缀自动补全
          'sass-loader', // sass当做css技术栈
        ],
      },
      {
     
        test: /\.jsx?$/, //适配js和jsx
        include: path.resolve(__dirname, './src'),
        use: {
     
          loader: 'babel-loader',
        },
      },
      {
     
        test: /\.(png|jpe?g|git)$/,
        include: path.resolve(__dirname, './src'),
        //use使用一个loader可以用对象,字符串,两个loader需要用数组
        use: {
     
          loader: 'file-loader',
          //options额外的配置,比如资源名称
          options: {
     
            //placeholder 占位符 [name]老资源模块的名称 [ext]老资源模块的后缀
            name: '[name]_[hash:6].[ext]',
            //打包后存放的位置
            outputPath: 'images/',
          },
        },
      },
      {
     
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        include: path.resolve(__dirname, './src'),
        use: 'file-loader',
      },
    ],
  },
  devtool: 'cheap-module-eval-source-map',
  watchOptions: {
     
    //不监听 node_modules 目录下的文件
    ignored: /node_modules/,
  },
  resolve: {
     
    modules: [path.resolve(__dirname, './node_modules')],
    alias: {
     
      react: path.resolve(
        __dirname,
        './node_modules/react/umd/react.production.min.js'
      ),
      'react-dom': path.resolve(
        __dirname,
        './node_modules/react-dom/umd/react-dom.production.min.js'
      ),
    },
    extensions: ['.js'],
  },
  devServer: {
     
    contentBase: './dist',
    open: true,
    port: 8081,
    hot: true,
    hotOnly: true, //即便HMR不生效,浏览器也不自动刷新,就开启hotOnly
    proxy: {
     
      '/api': {
     
        target: 'http://localhost:9092',
        changeOrigin: true,
      },
    },
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HardSourceWebpackPlugin(), //缓存文件
    new vConsolePlugin({
     
      enable: debug,
      filter: ['base'],
    }),
    new htmlWebpackPlugin({
     
      title: 'My App', //标题
      filename: 'index.html', // 输出的文件名,默认是index.html
      template: './src/index.html', // 模板文件路径
    }), //插件配置
    new CleanWebpackPlugin({
     
      //打包之前清理一次
      cleanOnceBeforeBuildPatterns: [
        path.resolve(__dirname, './dist'),
        path.resolve(__dirname, './build'),
      ],
    }),
  ],
})

module.exports = (env) => {
     //env为在指令中配置的参数,--env.debug,参见上文章节 >> 1.3、开发及生产环境分离
  let debug = null
  if (env && env.debug) debug = env.debug //如配置了--env.debug参数,则开启vConsolePlugin
  return devConfig(debug)
}

7、生产环境的配置代码

在webpack.config.pro.js文件中放入如下代码

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const PurifyCss = require('purifycss-webpack')
const glob = require('glob-all')
const tinyPngWebpackPlugin = require('tinypng-webpack-plugin')
const FilemanagerWebpackPlugin = require('filemanager-webpack-plugin')
const htmlWebpackPlugin = require('html-webpack-plugin')
const {
      CleanWebpackPlugin } = require('clean-webpack-plugin')

const proConfig = (zip) => ({
     
  entry: {
     
    base: ['core-js/stable', 'regenerator-runtime/runtime'],
    main: [path.resolve(__dirname, './src/index.js')],
  },
  output: {
     
    filename: 'js/[name]_[hash:6].js',
    path: path.resolve(__dirname, './build'),
  },
  // mode:"development",
  mode: 'production',
  module: {
     
    rules: [
      {
     
        test: /\.(css|scss)$/,
        include: path.resolve(__dirname, './src'),
        //loader是有顺序的,从后往前
        use: [
          // 'style-loader',//在页面插入css样式
          MiniCssExtractPlugin.loader,
          'css-loader', // 抽取css样式
          'postcss-loader', // 样式前缀自动补全
          'sass-loader', // sass当做css技术栈
        ],
      },
      {
     
        test: /\.jsx?$/, //适配js和jsx
        include: path.resolve(__dirname, './src'),
        use: {
     
          loader: 'babel-loader',
        },
      },
      {
     
        test: /\.(png|jpe?g|git)$/,
        include: path.resolve(__dirname, './src'),
        //use使用一个loader可以用对象,字符串,两个loader需要用数组
        use: {
     
          loader: 'file-loader',
          //options额外的配置,比如资源名称
          options: {
     
            //placeholder 占位符 [name]老资源模块的名称 [ext]老资源模块的后缀
            name: '[name]_[hash:6].[ext]',
            //打包后存放的位置
            outputPath: 'images/',
          },
        },
      },
      {
     
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        include: path.resolve(__dirname, './src'),
        use: 'file-loader',
      },
    ],
  },
  resolve: {
     
    modules: [path.resolve(__dirname, './node_modules')],
    alias: {
     
      react: path.resolve(
        __dirname,
        './node_modules/react/umd/react.production.min.js'
      ),
      'react-dom': path.resolve(
        __dirname,
        './node_modules/react-dom/umd/react-dom.production.min.js'
      ),
    },
    extensions: ['.js'],
  },
  optimization: {
     
    usedExports: true, //哪些导出的模块被使用了,再做打包
    splitChunks: {
     
      chunks: 'all', //所有的chunks代码公共部分分离出来成为一个单独的文件
      cacheGroups: {
     
        //缓存组
        react: {
     
          test: /react|react-dom/,
          name: 'react',
          minChunks: 1,
        },
      },
    },
  },
  plugins: [
    new PurifyCss({
     
      // 清除无用css
      paths: glob.sync([
        // 要做css Tree Shaking的路径文件
        path.resolve(__dirname, './src/*.html'),
        path.resolve(__dirname, './src/*.js'),
      ]),
    }),
    new OptimizeCssAssetsPlugin({
     
      //压缩css
      cssProcessor: require('cssnano'), //引入cssnano配置压缩选项
      cssProcessorOptions: {
     
        discardComments: {
      removeAll: true },
      },
    }),
    new MiniCssExtractPlugin({
     
      //提取css为单独文件
      filename: 'css/[name]_[contenthash:6].css',
    }),
    new tinyPngWebpackPlugin({
     
      key: [
        '----------这里填入申请到的API key--------',
        '----------这里填入申请到的API key--------',
        '----------这里填入申请到的API key--------',
        '----------这里填入申请到的API key--------',
        '----------这里填入申请到的API key--------',
      ],
      ext: ['png', 'jpeg', 'jpg', 'gif'],
    }),
    zip
      ? new FilemanagerWebpackPlugin({
     
          onEnd: {
     
            copy: [
              {
     
                source: path.resolve(__dirname, './build'),
                destination: path.resolve(__dirname, './tmp_for_zip/dist'),
              },
            ],
            archive: [
              {
     
                source: path.resolve(__dirname, './tmp_for_zip'),
                destination: path.resolve(__dirname, './dist.zip'),
              },
            ],
            delete: [path.resolve(__dirname, './tmp_for_zip')],
          },
        })
      : () => {
     },
    new htmlWebpackPlugin({
     
      title: 'My App', //标题
      filename: 'index.html', // 输出的文件名,默认是index.html
      template: './src/index.html', // 模板文件路径
      minify: {
     
        //压缩HTML文件
        removeomments: true, // 移除HTML中的注释
        collapseWhitespace: true, // 删除空白符和换行符
        minifyCss: true, //压缩内联css
      },
    }), //插件配置
    new CleanWebpackPlugin({
     
      //打包之前清理一次
      cleanOnceBeforeBuildPatterns: [
        path.resolve(__dirname, './dist'),
        path.resolve(__dirname, './build'),
      ],
    }),
  ],
})

module.exports = (env) => {
     //env为在指令中配置的参数,--env.debug,参见上文章节 >> 1.3、开发及生产环境分离
  let zip = null
  if (env && env.zip) zip = env.zip//如配置了--env.zip参数,则打一个zip的压缩包
  return proConfig(zip)
}

8、项目的github地址

https://github.com/hyj0703/webpack

你可能感兴趣的:(webpack,javascript,node.js,github,babel,es6)