re0:从零开始的Webpack4配置

从零开始的Webpack4配置

作为一个前端工程师,不管怎么说,webpack配置还是要了解一下的。虽然一般情况下都用不上,但是人无我有,人有我优,是不是就能在市场有有一点优势了呢?学了webpack之后,以后要在项目中装其他乱七八糟的插件的时候也不会一脸懵逼,不知道这些东西怎么配置的,免得每次配东西都要百度。话不多说,开始学习吧!

这是根据知乎上看到的文章写的,我照着教程走了一遍,为了巩固学习成果,写篇文章记录一下。我学习过程中的代码都在这里

webpack 的核心价值就是前端源码的打包,即将前端源码中每一个文件(无论任何类型)都当做一个 pack ,
然后分析依赖,将其最终打包出线上运行的代码。webpack 的四个核心部分
 - entry 规定入口文件,一个或者多个 	
 - output 规定输出文件的位置 	
 - loader 各个类型的转换工具 	
 - plugin   打包过程中各种自定义功能的插件

基础配置

初始化环境

首先创建一个文件夹my_webpack,然后npm init -y,-y 命令表示在你初始化的时候所有的选择yes or no的选项全部选yes,也就是说全部使用默认配置来初始化。然后安装webpack,npm i webpack webpack-cli -D,-D表示依赖安装到开发环境,生产环境中不会用到,对应到package.json文件的话,就是会添加到devDependencies下面。
然后创建src目录,在src目录下创建index.js文件,里面随便写点console.log('webpack start')。然后在根目录下创建webpack.config.js,内容如下:

	// path模块是node.js中提供用于处理文件路径和目录路径的实用工具
	const path = require('path')
	
	module.exports = {
	    // mode 可选 development 或 production ,默认为后者
	    // production 会默认压缩代码并进行其他优化(如 tree shaking)
	    mode: 'development',
	    // __dirname就是src目录之前的目录结构,是根据文件地址自动生成的
	    // path.join()就是把参数拼接成目录结构,如以下的entry值就是 __dirname/src/index.js
	    // path的具体API可以参考node.js
	    entry: path.join(__dirname, 'src', 'index'),	
	    output: {
	        filename: 'bundle.js', 	// 表示打包出来的压缩文件名
	        // 表示打包出来的文件放在那个目录下面,这里指: __dirname/dist/ 下面
	        path: path.join(__dirname, 'dist')		
	    }
	}

然后在package.json里的scripts里添加打包命令

	"scripts": {
	    "build": "webpack"
	  },

然后运行npm run build命令,就可以看到打包文件放在dist目录下面了。

区分dev与build

使用 webpack 需要两个最基本的功能:第一,开发的代码运行一下看看是否有效;第二,开发完毕了将代码打包出来。这两个操作的需求、配置都是完全不一样的。例如,运行代码时不需要压缩以便 debug ,而打包代码时就需要压缩以减少文件体积。因此,这里我们还是先把两者分开,方便接下来各个步骤的讲解。

这里先看看path.join()方法的执行结果,以便接下来的理解

在这里插入图片描述
首先,安装 npm i webpack-merge -D ,那么webpack-merge是干什么的呢?

	// webpack-merge做了两件事:它允许连接数组并合并对象,而不是覆盖组合
	const merge = require("webpack-merge");
	merge(
	    {a : [1],b:5,c:20},
	    {a : [2],b:10, d: 421}
	)
	//合并后的结果
	{a : [1,2] ,b :10 , c : 20, d : 421}

然后根目录新建 build 目录,其中新建如下三个文件。

	// webpack.common.js 开发和成产环境都用的到的公共配置
	const path = require('path')
	const srcPath = path.join(__dirname, '..', 'src')
	const distPath = path.join(__dirname, '..', 'dist')
	module.exports = {
	    entry: path.join(srcPath, 'index')		// 就是指的 src/index.js
	}
	// webpack.dev.js 运行代码的配置(该文件暂时用不到,先创建了,下文会用到)
	const path = require('path')
	const webpackCommonConf = require('./webpack.common.js')	// 引入公共配置
	const { smart } = require('webpack-merge')
	const srcPath = path.join(__dirname, '..', 'src')
	const distPath = path.join(__dirname, '..', 'dist')
	// 那么这里使用 webpack-merge 的方法就可以把公共配置与开发环境的配置合并在一起了
	module.exports = smart(webpackCommonConf, {
	    mode: 'development'
	})
	// webpack.prod.js 打包代码的配置
	const path = require('path')
	const webpackCommonConf = require('./webpack.common.js')
	const { smart } = require('webpack-merge')
	const srcPath = path.join(__dirname, '..', 'src')
	const distPath = path.join(__dirname, '..', 'dist')
	// 那么这里使用 webpack-merge 的方法就可以把公共配置与生产环境的配置合并在一起了
	module.exports = smart(webpackCommonConf, {
	    mode: 'production',
	    output: {
	    	// contentHash指哈希码,这里 :8指的是取哈希码的前8位
	        filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
	        path: distPath,
	        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
	    }
	})

修改 package.json 中的 scripts

	"scripts": {
		/* 表示打包时要用到webpack.prod.js中的配置 */
	   "build": "webpack --config build/webpack.prod.js"
	 },

重新运行 npm run build 即可看到打包出来的代码。最后,别忘了将根目录下的 webpack.config.js 删除。

这将引发一个新的问题:js 代码中将如何判断是什么环境呢?需要借助 webpack.DefinedPlugin 插件来定义全局变量。可以在 webpack.dev.jswebpack.prod.js 中做如下配置:

	// 引入 webpack
	const webpack = require('webpack')
	
	// 增加 webpack 配置
	plugins: [
		new webpack.DefinePlugin({
			// 注意:此处 webpack.dev.js 中写 'development' ,webpack.prod.js 中写 'production'
			ENV: JSON.stringify('development')
		})
	]

最后,修改 src/index.js 只需加入一行 console.log(ENV) ,然后重启 npm run dev 即可看到效果。

JS 模块化

webpack 默认支持 js 各种模块化,如常见的 commonJSES6 Module 。但是推荐使用 ES6 Module因为 production 模式下,ES6 Module 会默认触发 tree shaking ,而 commonJS 则没有这个福利。究其原因,ES6 Module 是静态引用,在编译时即可确定依赖关系,而 commonJS 是动态引用。
es6语法不熟的可以参考这里

启动本地服务
上文创建的 webpack.dev.js 一直没使用,下面就要用起来。

使用 html

启动本地服务,肯定需要一个 html 页面作为载体,新建一个 src/index.html 并初始化内容。

	<!DOCTYPE html>
	<html>
	<head><title>Document</title></head>
	<body>
	    <p>this is index html</p>
	</body>
	</html>

要使用这个 html 文件,还需要安装 npm i html-webpack-plugin -D ,然后配置 build/webpack.common.js ,因为无论 dev 还是 prod 都需要打包 html 文件。

	const HtmlWebpackPlugin = require('html-webpack-plugin');
	
	plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html'
        })
    ]

重新运行 npm run build 会发现打包出来了 dist/index.html ,且内部已经自动插入了打包的 js 文件。

webpack-dev-server

有了 htmljs 文件,就可以启动服务了。首先安装 npm i webpack-dev-server -D ,然后打开 build/webpack.dev.js配置。只有运行代码才需要本地 server ,打包代码时不需要。

	  // 启动本地服务器需要安装webpack-dev-server模块,然后在开发模式下配置devServer,生产模式下不需要
	  devServer: {
		port: 3001,	// 端口号
		progress: true,	// 显示打包的进度条
		contentBase: distPath,	// 根目录
		open: true,	// 自动打开浏览器
    	compress: true,	// 启动gzip压缩
	 },

打开 package.json 修改 scripts ,增加 "dev": "webpack-dev-server --config build/webpack.dev.js", 。然后运行 npm run dev ,打开浏览器访问 localhost:3001 即可看到效果。

解决跨域

实际开发中,server 端提供的端口地址和前端可能不同,导致 ajax 收到跨域限制。使用 webpack-dev-server 可配置代理,解决跨域问题。如有需要,在 build/webpack.dev.js 中增加如下配置。

	devServer: {
	    proxy: {
	      // 将本地 /api/xxx 代理到localhost:3000/api/xxx
	      '/api': 'http://localhost:3000',
	      // 将本地 /api2/xxx 代理到localhost:3000/xxx
	      '/api2': {
	        target: 'http://localhost:3000',
	        changeOrigin: true, // 允许跨域
	        pathRewrite: {
	          '/api2': ''
	        }
	      }
	    }
	},

处理 ES6

使用 babel

由于现在浏览器还不能保证完全支持 ES6 ,将 ES6 编译为 ES5 ,需要借助 babel 这个神器。安装 babel npm i babel-loader @babel/core @babel/preset-env -D ,然后修改 build/webpack.common.js 配置

	module: {
	   rules: [
	       {
	           test: /\.js$/,	// 正则匹配 .js文件
	           loader: ['babel-loader'],		// babel-loader转换es6代码
	           include: srcPath,	// 检测的js文件位于这里
	           exclude: /node_modules/		// 这里的js文件不会被检测到
	       },
	   ]
	},

还要根目录下新建一个 .babelrc json 文件,内容下:

	{
	    "presets": ["@babel/preset-env"],
	    "plugins": []
	}

src/index.js 中加入一行 ES6 代码,如箭头函数 const fn = () => { console.log('this is fn') } 。然后重新运行 npm run dev,可以看到浏览器中加载的 js 中,这个函数已经被编译为 function 形式。

使用高级特性

babel 可以解析 ES6 大部分语法特性,但是无法解析 class 、静态属性、块级作用域,还有很多大于 ES6 版本的语法特性,如装饰器。因此,想要把日常开发中的 ES6 代码全部转换为 ES5 ,还需要借助很多 babel 插件。
安装 npm i @babel/plugin-proposal-class-properties @babel/plugin-transform-block-scoping @babel/plugin-transform-classes -D ,然后配置 .babelrc

	{
	    "presets": ["@babel/preset-env"],
	    "plugins": [
	        "@babel/plugin-proposal-class-properties",
	        "@babel/plugin-transform-block-scoping",
	        "@babel/plugin-transform-classes"
	    ]
	}

src/index.js 中新增一段 class 代码,然后重新运行 npm run build ,打包出来的代码会将 class 转换为 function 形式。

source map

source map 用于反解析压缩代码中错误的行列信息,dev 时代码没有压缩,用不到 source map ,因此要配置 build/webpack.prod.js

module.exports = smart(webpackCommonConf,{
  // 生产环境下推荐使用1或3,生成独立的map文件
  // source map 用于反解析压缩代码中错误的行列信息,dev时代码没有压缩,所以用不到source map,所以用在prod中
  // devtool: 'source-map',  // 1、生成独立的source-map文件
  // devtool: 'eval-source-map', // 2、不会生成独立的文件,集成到打包出来的js文件中  
  // devtool: 'cheap-moudle-source-map',  // 3、生成单独地souce map文件,但没有列信息(因为文件体积较小)
  devtool: 'cheap-module-eval-source-map',  // 4、同3,但不会生成独立的文件,集成到打包出来的js文件中
}}

生产环境下推荐使用 1 或者 3 ,即生成独立的 map 文件。修改之后,重新运行 npm run build ,会看到打包出来了 map 文件。

处理样式

处理 css

安装必要插件 npm i style-loader css-loader -D ,然后配置 build/webpack.common.js

	 module: {
	     rules: [
	         { /* js loader */ },
	         {
	             test: /\.css$/,	// 正则匹配 .css文件
	             loader: ['style-loader', 'css-loader']  // loader 的执行顺序是:从后往前
	         }
	     ]
	 },

新建一个 css 文件,然后引入到 src/index.jsimport './css/index.css' ,重新运行 npm run dev 即可看到效果。

处理 less

less sass 都是常用 css 预处理语言,以 less 为例讲解。安装必要插件 npm i less less-loader -D ,然后配置 build/webpack.common.js

	module: {
	  rules: [
	        { /* js loader */ },
	        {
	            test: /\.(css|less)$/,		// 可以同时匹配 .css和 .less文件
	    		loader: ['style-loader', 'css-loader', 'less-loader']  // 增加 'less-loader' ,注意顺序
	        }
	    ]
	},

新建一个 less 文件,然后引入到 src/index.jsimport './css/index.less' ,重新运行 npm run dev 即可看到效果。

自动添加前缀

一些 css3 的语法,例如 transform: rotate(45deg); 为了浏览器兼容性需要加一些前缀,如 webkit- ,可以通过 webpack 来自动添加。安装 npm i postcss-loader autoprefixer -D ,然后配置build/webpack.common.js

	module: {
	  rules: [
	        { /* js loader */ },
	        {
	        // 添加css、less转换器,postcss-loader用于给css属性加浏览器兼容前缀,如webkit-,此外还需要创建postcss.config.js文件
			test: /\.(css|less)$/,
			loader: ['style-loader','css-loader','less-loader','postcss-loader']	// loader的执行顺序是从后往前
	        }
	    ]
	},

还要新建一个 postcss.config.js 文件,内容是

	module.exports = {
	    plugins: [require('autoprefixer')]
	}

重新运行 npm run dev 即可看到效果,自动增加了必要的前缀。

抽离 css 文件

默认情况下,webpack 会将 css 代码全部写入到 html<style> 标签中,但是打包代码时需要抽离到单独的 css 文件中。安装 npm i mini-css-extract-plugin -D 然后配置 build/webpack.prod.js(打包代码时才需要,运行时不需要)

	// 引入插件
	const MiniCssExtractPlugin = require('mini-css-extract-plugin')
	
	// 增加 webpack 配置
    module: {
        rules: [
            {
                test: /\.(css|less)$/,  // 需要抽离的样式文件
			    loader: [
			       // MiniCssExtractPlugin用于在打包时将css抽离到单独的css文件中
			        MiniCssExtractPlugin.loader,  // 这里不再使用style-loader
			        'css-loader',
			        'less-loader',
			        'postcss-loader']
			 }
	   ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/main.[contentHash:8].css'
        })
    ]

如需要压缩 css ,需要安装 npm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D ,然后增加配置build/webpack.prod.js

	// 引入插件
	const TerserJSPlugin = require('terser-webpack-plugin')
	const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
	
	// 增加 webpack 配置
    optimization: {
        minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
    },

运行 npm run build 即可看到打包出来的 css 是独立的文件,并且是被压缩过的。

处理图片

要在 jsimport 图片,或者在 css 中设置背景图片。安装 npm i file-loader -D 然后配置 build/webpack.common.js

	module: {
		rules: [{
			// 用来处理在js中import图片,在css中使用背景图片的操作
			test: /\.(png|jpg|gif|PNG|JPG|GIF)$/,
			use: 'file-loader'
		}]
	}

打包之后,dist 目录下会生成一个类似 917bb63ba2e14fc4aa4170a8a702d9f8.jpg 的文件,并被引入到打包出来的结果中。

多页应用

src 下有 index.js index.htmlother.js other.html ,要打包输出两个页面,且分别引用各自的 js 文件。

第一,配置输入输出

	// 在webpack.common.js中配置入口文件
	// 多页应用
	entry: {
		index: path.join(srcPath,'index.js'),
		other: path.join(srcPath,'other.js')
	},
  // 在webpack.prod.js文件中配置出口文件
  output: {
    // filename: 'bundle.[contentHash:8].js',  //打包代码时,加上hash戳
    filename: '[name].[contentHash:8].js',  // 多页应用时打包出来的文件名不一样
    path: distPath,
    // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件url的前缀
  },

第二,配置 html 插件

 	plugins: [
        // 生成 index.html
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
            chunks: ['index']  // 只引用 index.js
        }),
        // 生成 other.html
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other']  // 只引用 other.js
        }),
     ]
抽离公共代码

多个页面或者入口,如果引用了同一段代码,如上文的多页面例子中,index.jsother.js 都引用了 import './common.js' ,则 common.js 应该被作为公共模块打包。webpack v4 开始弃用了 commonChunkPlugin 改用 splitChunks ,可修改 build/webpack.prod.js 中的配置

module.exports = smart(webpackCommonConf,{
	optimization: {
        // 分割代码块
        splitChunks: {
            // 缓存分组
            cacheGroups: {
                // 公共的模块
                common: {
                    chunks: 'initial',
                    minSize: 0,  // 公共模块的大小限制
                    minChunks: 2  // 公共模块最少复用过几次
                }
            }
        }
    },
})

重新运行 npm run build ,即可看到有 common 模块被单独打包出来,就是 common.js 的内容。

第三方模块

同理,如果我们的代码中引用了 jquery lodash 等,也希望将第三方模块单独打包,和自己开发的业务代码分开。这样每次重新上线时,第三方模块的代码就可以借助浏览器缓存,提高用户访问网页的效率。修改配置文件,增加下面的 vendor: {...} 配置。

  optimization: {
    // 此配置用于打包时压缩css文件
    minimizer: [new TerserWebpackPlugin({}), new OptimizeCssAssetsWebpackPlugin({})],
    // 分割代码块,用于抽离公共代码
    splitChunks: {
      // 缓存分组
      cacheGroups: {
        // 第三方模块
        vendor:{
          priority: 1,  // 权限更高,优先抽离,很重要!!
          test: /node_modules/,
          chunks: 'initial',
          minSize: 0, // 大小限制
          minChunks: 1  // 最少复用过几次
        },
        // 公用的模块
        common: {
          chunks: 'initial',
          minSize: 0, // 公共模块的大小限制
          minChunks: 2, //  公共模块最少复用过几次
        }
      }
    }
  }

重启 npm run build ,即可看到 vendor 模块被打包出来,里面是 jquery 或者 lodash 等第三方模块的内容。

懒加载

webpack 支持使用 import(...) 语法进行资源懒加载。安装 npm i @babel/plugin-syntax-dynamic-import -D 然后将插件配置到 .babelrc 中。

新建 src/dynamic-data.js 用于测试,内容是 export default { message: 'this is dynamic' } 。然后在 src/index.js 中加入

	setTimeout(() => {
	    import('./dynamic-data.js').then(res => {
	        console.log(res.default.message)  // 注意这里的 default
	    })
	}, 1500)

重新运行 npm run dev 刷新页面,可以看到 1.5s 之后打印出 this is dynamic 。而且,dynamic-data.js 也是 1.5s 之后被加载进浏览器的 —— 懒加载,虽然文件名变了。

重新运行 npm run build 也可以看到 dynamic-data.js 的内容被打包一个单独的文件中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值