webpack

106 阅读8分钟
  1. 工作模式
    • yarn webpack --mode [opition]
      • production 自动启动优化选项,将打包后的文件压缩
      • development 自动优化打包的速度,添加调试的辅助
      • no 使用最原始的大包方式,不会做任何处理
    • 另外一种配置选择工作模式,可以直接在配置文件中配置
const path = require('path')
module.exports = {
	mode: 'development', // 模式
	entry: './src/index.js', // 入口文件,可以相对路经
	output: {
		filename: 'bundle.js', // 打包文件名,默认是dist文件下的main.js
		path:path.join(__dirname,'dist') // 必须是绝对路径
	}
}

  1. 资源模块加载
const path = require('path')
module.exports = {
	mode: 'development',
	entry: './src/main.css',
	output: {
		filename: 'bundle.js',
		path:path.join(__dirname,'dist')
	},
	module: {
		rules:[
			{
				text:/.css$/, // 正则表达式,用来匹配在打包过程中所遇到的文件路径,以.css结尾
				use: 'css-loader', // 用来处理匹配的文件使用的loader
			}
		]
	}
	
}

- 添加上述配之后,打包之后的css文件并没有生效,原因:css-loader 只是将样式转换成js模块,这样才能正常打包,但是并没有被使用
- yarn add style-loader --dev 添加style-loader 会将css-loader 转换后的结果,以标签的形式 追加到页面上

3. loader是webpack的核心,通过不同的loader就可以加载任何类型的资源

  1. 导入资源模块
import createHeading from './heading'
import './main.css' // 导入资源文件
const heading = createHeading()
document.body.append(heading)

5.文件资源加载器 - 大多数加载器都是像cssLoad一样 将文件转化成js,但是有些资源例如字体,图片,是没有办法通过js的方式去表示,则对于这种资源,需要用到文件资源加载器 - yarn add file-loader --dev

module: {
		rules:[
			{
				test:/.css$/, // 正则表达式,用来匹配在打包过程中所遇到的文件路径,以.css结尾
				use:[ 'style-loader','css-loader'], // 用来处理匹配的文件使用的loader
			},
			{
				test: /.png$/,
				use: 'file-loader'
			}
		]
	}
  • webpack 因为index.html文件并没有生成到dist路径下,webpack默认生成的img图片和index.html同级,可通过publicPath 配置,publicPath默认为空,代表根目录,
output: {
		filename: 'bundle.js',
		path:path.join(__dirname,'dist'),
		publicPath: 'dist/' // 代表根目录下得dist文件下得路径位置
	},
  1. URL加载器 (和文件资源加载器一样可以加载静态资源) - Data URLs 直接表示一个文件,既不会发http请求,也不会生成独立文件 - yarn add url-loader - 这种方式比较适合体积比较小的资源,如果资源比较大的情况,就会导致打包结果比较大,从而影响运行速度 - 小文件使用这种方式Data URLs 打包,减少请求次数 - 大文件单独提取存在,提高加载速度
	{
            // 超出10kb会使用file-loader的时候打包,不然使用url-loader,所以使用url-loader的方式时,也要同时安装file-loader
            test: /.png$/,
            use: {
                    loader: 'url-loader',
                    size: 10 * 1024 ,// 限制10kb时候,应乘以1024,因为单位默认是字节,
            }

      }
  1. 常见加载器分类
    • 编译转换类 如 css-loader 将css转换成js模块
    • 文件操作类 如 file-loader
    • 代码检查类 如 eslint-loader
  2. webpack与ES2015
    • 由于webpack默认就可以处理import 与export ,但是webpack不能编译es6的代码,因为模块打包需要,所以webpack裁处理了import与export
    • yarn add bable-loader @babel/core @babel/preset-env --dev
	{
            test: /.js$/,
            use: {
                    // babel-loader 只是提供了es6转换新特性的平台,具体需要怎么转换还需要引入options中提供的插件
                    loader:'babel-loader',
                    options: ['@babel/preset-env']

            }
        }
  1. webpack 加载资源的方式
    • 通过ES Module标准的import声明
    • 通过 CommonJs 标准的 require函数
  2. webpack 核心工作原理
    • loader机制是webpack的核心
    • 如果没有loader webpack只能是合并js代码的工具
  3. webpack开发一个loader
    • 每一个loader都是一个函数,接受一个要转换的参数,返回一个结果,但是返回的结果必须是一个字符且符合js语法
    • loader负责资源从输入到输出的转换,对同一个资源可以依次使用多个loader
        {
                test: /.md$/,
                use:['html-loader', './markdown-loader.js'] // html-loader 将markdowen-loader导出的字符串 传递给html-loader,转换成js代码
        }
const {marked} = require('marked')
module.exports = (source) => {
	const html = marked(source)
	return `module.exports = ${JSON.stringify(html)}` 
        // 或者
        return `exports default = ${JSON.stringify(html)}` 
        
}

  1. 插件机制
    • 目的是为了增强webpack自动化能力
    • loader专注实现资源模块加载,从而实现整体项目的大包,而Plugin解决其他自动化的工作
    • 比如 Plugin实现在每一次打包之前 清除dist目录,拷贝不需要打包的资源到输出目录,或者压缩打包的代码
    • webpack+ Plugin实现大多前端工程化工作
  2. webpack 常见插件
    • clean-webpack-plugin 自动清除输出目录,清除不需要的东西
    • html-webpack-plugin 自动生成使用bundle.js(output属性配置的输出路径)的html,wbpack知道自己生成了多少个bundle.js 文件,并自动在生成的html文件中引用
    • 同时输入多个页面
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title><%= htmlWebpackPlugin.options.title %></title> 
        // 这种语法对应lodash.template
</head>
<body>
	<script src="../dist/bundle.js" ></script>
</body>
</html>

plugins: [
new htmlWebpackPlugin({
                    title: 'webpack chenyun update',
                    meta: {
                            viewport:'width=device-width'
                    },
                    template: './src/index.html',
            })
]
多文件
new htmlWebpackPlugin({
        title: 'webpack chenyun update',
        meta: {
                viewport:'width=device-width'
        },
        template: './src/index.html',
}),
new htmlWebpackPlugin({
        filename:'about.html',
        template: './src/index.html',
})
  1. Plugin插件核心原理
    • Plugin通过钩子机制实现
    • 必须是一个函数或者是一个包含apply方法的对象
    • apply 方法会在webpack启动时自动调用,传入的参数包含了这次构建的所有配置信息
    • 插件通过在生命周期的钩子中挂载函数实现扩展
class MyPlugin {
	apply(compiler){
		console.log('webPack 启动的时候,该方法自动调用');
               // emit 是钩子函数,MyPlugin是插件名称,箭头函数式挂载的函数
		compiler.hooks.emit.tap('MyPlugin',compilation => {
			// compilation 可以理解为此次打包的上下文
			for(let name in compilation.assets) {
				console.log(name);
				if(name.endsWith('.js')){
					const contents = compilation.assets[name].source()
					const withoutContents = contents.replace(/\/\*\*+\*\//g,'') // 去掉打包文件的注释
					compilation.assets[name]={
						source:()=> withoutContents,
						size: ()=>withoutContents.lenght
					}
				}

			  
			}
			

		})

	}
}
  1. webpack自动编译
    • yarn webpack -- watch,监听文件是否发生改变,一旦改变就自动编译
  2. webpack自动刷新浏览器
    • 安装一个插件 自己百度
  3. webpack dev serve
    • webpack Dev Serve 提供用户开发的http server,集成自动编译和自动刷新浏览器等功能

    • yarn add webpack-dev-server --dev

    • yarn webpack-dev-serve 启动

    • webpack-dev-serve 为了提高开发效率,并不会每次编译打包都会生成到dist目录,将打包结果暂时存在在内存当中,而http server又从内存当中将文件读出来,发送给浏览器,这样就不会频繁的读写磁盘操作,从而提高工作效率

    • yarn webpack-dev-serve open 自动唤起浏览器,打开运行地址

  4. webp-dev-serve 配置静态资源
    • 主要是webpack的输出文件,都可以直接被访问,如果有其他静态资源也需要作为开发服务器的资源被访问到,需要额外告诉webpack-dev-serve
    • devServe 属性就是专门为webp-dev-serve配置的选项
devServe: {
                // 指定静态资源路径
		contentBase:'./public'
	},
  1. webpack-dev-serve 代理api
    • 跨域资源共享(cors),使用cors的前提是api必须支持,并不是任何情况下api都支持,前后端如果同源部署,并没有必要去部署cors
    • 支持配置代理
devServe: {
    contentBase: './public',
    proxy: {
            '/api': {
                    // http//:localhost:8080/api/users -> https://api.github.com/api/users
                    target:  'https://api.github.com',
                    // 实际请求的地址是:https://api.github.com/users,则需要重写路径
                    pathRewrite: {
                            '/^api': '' // 以api开头的 替换成""
                    },
                    // 不使用localhost:8080 作为请求github的主机名
                    changeOrign: true
            }
    }
},
  1. source Map 源代码地图
    • sourece map 解决了源代码与运行代码不一致产生的问题,可以将线上的运行代码转换成源代码 22.webpack配置sourece Map
    • 有12种模式,每种方式的效率和效果不同
    • devtool 就是配置source map
devtool:'soure-map',
   -  eval模式   
      eval可以运行传递给它的字符串,比如eval('console.log(111)')
      devtool:eval模式,不能生成sourceMap只能定位出现问题的源代码的文件名称,但是无法定位到具体的位置行列信息
   - eval source map
       生成sourceMap,能够准确定位行列信息
   - cheap eval source map
       生成sourceMap,但是只能定位到行信息,不能定位到列信息 
   - cheap module eval source map
       和cheap eval source map效果一样,不过cheap eval source map 生成的map代码 已经将es6转换成es5代码,而cheap module eval source map没有
   - inline-source-map 
       将生成的source map代码嵌入到源代码中,这种一般不会使用,因为这会使打包的代码体积会变大很多
    - nosoucer-souce-map
        能定位行信息,但是看不到源代码
    - ...
  • 根据个人习惯不同的开发环境选择不同的模式
    1. 本地开发模式 选择 eval source map,能够准确定位信息
    2. 线上环境 选择no或者nosouces-souce-map 会暴漏源代码,防止别有用心之人,线上测试环境更倾向于nosouces-souce-map ,这样不会暴漏源代码,虽然看不到源代码,但是可以定位出现问题的行数
  1. HMR(hot module Replacement) - 模块热替换或者模块热更新

    webpack 自身的刷新机制,页面状态会丢失
    webpack的模块热替换主要指 应用运行过程中实时替换某个模块,应用运行状态不受影响
    HMR已经集成在webpack-dev-server
     
    
    • webpack的HMR并不是开箱即用,需要我们手动处理模块热替换逻辑,但是开启热更新后,样式文件的热更新是可以使用的,因为style-loader已经手动处理热更新 。样式模块的替换是有规律的,直接替换掉样式的值就行,所以有一个统一的替换方案,style-loader顺便给处理了。但是js是没有规律的,所以需要我们自己手动处理。
    • 当我们使用框架搭建项目的时候,不需要手动处理热更新了,因为框架下的每种文件都是有规律的,框架内部已经继承了HRM方案
    1. HMR APIs module.hot.accept(a,b) module.hot.accept 是HMR api的核心,参数a代表以来的模块,b代表当依赖的模块发生改变后的处理函数
    2. 处理js模块热替换
      module.hot.accept('/editor',()=> {
            const value = lastEditor.innerHTML
            document.body.removeChild(lastEditor)
            const newEditor = createEditor()
            newEditor.innerHTML = value
            document.body.appendChild(editor)
            lastEditor = newEditor
      })
      
    3. 处理图片的热替换
          module.hot.accept('./better.png',()=> {
              img.src = background
          })
      

    4.HMR注意事项

    • hot:true,hotOnly的区别
            devServer: {
                static:'./',
                host: "localhost",
                port: 8080,
                hot:true
            },
            plugins: [
    	new htmlWebpackPlugin(),
    	new webpack.HotModuleReplacementPlugin()
            ]
    

    处理HMR的代码如果出现错误会自动刷新,自动刷新之后,页面的错误代码也会被清除,报错信息也会消失,这样的话不容易发现出错的位置. 为了能够找到处理HMR代码出现错误的地方,将hot:true,改为hot:'only';,即可

    • 如果插件中未实例化 HotModuleReplacementPlugin插件,则直接使用module.hot方法会报错,所以使用module.hot方法之前应该先判断下。

  2. 不同环境下的webpack配置

    • 配置文件根据环境的不同导出不同的配置

    module.exports = (env,args)=> {
            let config = {
                    module: {
                    rules: [
                            {
                                    test:/\.(png)$/,
                                    use:'file-loader'
                            }
                    ],
                    },
            } // 公共配置信息
            if(env == 'production'){
                    config.mode = 'production'
                    config.devtool = false
                    ...
            }else {
    
            }
    
    }
    
            或者直接在module.export = {
                通过全局变量判断
                if(process.env.NODE_ENV == 'production'){}
            }
    
    • 一个环境一个配置文件 image.png 为什么不用Object.assign,因为Object.assign 该函数,后面的对象会把前一个对象的同名属性完全替换,而目前我们只是想合并同名属性,可以使用webpack-merge提供的函数,将多个配置信息合 并
        yarn webpack --config webpack.prod.js
    
  3. DefinePlugin插件 为代码注入全局成员

plugins: [
		new htmlWebpackPlugin(),
		new webpack.HotModuleReplacementPlugin(),
		new CleanWebpackPlugin(),
		
		// 值必须用引号包裹一层,即使是字符串
		new webpack.DefinePlugin({
			// API_BASE_URL:'http:api.example.com'
			API_BASE_URL: "'http:api.example.com'" // JSON.stringify('http:api.example.com')
		})
	]
  1. Tree-shaking 摇掉代码中没有被用到的部分

    webpack中的配置,如果mode是production,就自动开启这个功能

    • Tree-shaking不是指某个配置的选项,而是一组功能搭配使用后的优化效果,这种效果会在生产模式下自动启用
    optimization:{
        usedExports: true,// 只导出用到的引用
        concatenateModules:true,//尽量将所有的打包模块合并输出到一个函数中,即提升了运行效率,又会减少运行体积
        minimize:true // 移除未引用变量的定义代码
    }
    
    使用yarn webpack -- no 就可以看到和生产模式一样的效果
  1. 代码分割/代码分隔

    • 方式1:多入口打包

      适用于多页面程序,一个页面对应一个打包入口,不同页面对应的公共部份单独提取

entry:{
		index:'./src/index.html',
		album:'./src/album.html'
	},
output: {
		filename: '[name].bundle.js',
		path: path.join(__dirname,'dist')
	},
plugins:[
    htmlWebpackPlugin生成的html文件默认引入打包的所有js文件,如果不想这样,可以通过chunks指定引入哪一个打包后多文件,chunks中的值对应output中的name,而output中的name又对应entry中的键值
    new htmlWebpackPlugin({
			title:'Mulity-Entry ',
			template:'./src/index.html',
			filename:'index.html',
                        chunks:['index']

		}),
		new htmlWebpackPlugin({
			title:'Mulity-Entry ',
			template:'./src/album.html',
			filename:'album.html',
                        chunks:['album']

		}),
]

image.png

index.html和album.html两个文件都使用了global.css和fetch.js,我们可以将其单独打包

optimization:{
		splitChunks:{
			chunks:all
		}
	}
    会生成ablum-index.bundle.js文件,公共的文件都会被打包到这里

image.png

  • 方式2:动态导入:需要用到哪一个模块,再加载这个模块

    动态导入的模块会被自动分包,相对于多入口打包,这种方式更加灵活

image.png

yarn webpack,自动分包,如下生成了三个bundle.js,其中两个bundle.js 分别对用album,index两个组件,另外一个bundle.js 对应 album,index两个组件的公共部分。单页面应用可以使用这种方式

image.png

28.MinCssExtractPlugin插件 提取css到单个文件

- yarn add min-css-extra-plugin -dev

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
		rules: [
			{
				test:/\.(png)$/,
				use:'file-loader'
			},
			{
				test:/\.css$/,
				use:[
					MiniCssExtractPlugin.loader,
					css-loader
				]
				

			}
		],
	},
plugins:[
    new MiniCssExtractPlugin(),// 自动提取打包后的css文件到单独的一个文件中
]
new MiniCssExtractPlugin(),

经过css-loader,style-loader处理后的结果,通过style标签的形式注入到页面当中,从而使样式可以工作,而如果使用MiniCssExtractPlugin插件进行处理,则会生成单独的文件,并以link的形式注入到文件。这两种打包css的方式,可以分体积进行区分,如果css比较大的话,使用该插件,不然还是使用style-loader处理比较好,这样可以减少一次请求

  1. optimize-css-assets-webpack-plugin 压缩代码 上面的css抽取插件,在生产模式打包后,会发现css文件并没有被压缩,这是因为webpack内置的压缩功能只是用来针对js文件,对于其他文件的压缩都需要额外的插件来支持
不要将该插件配置在plugins中,可以配置在optimization中的minimizer中,搭配minimize:true(该属性在生产模式下自动开启),这样css压缩才会生效。如果开发模式下我们不设置minimize,则不会生效。如果将该插件配置在plugins中,则在任何模式下都会生效,有时候开发模式我们并不想压缩代码

optimization:{
    如果我们使用了minimizer压缩器这个属性,则webpack认为我们自定义使用哪一种压缩器。如果我们仅仅实例化OptimizeCssAssetsPlugin这个插件,则js就不会被压缩,js压缩要通过terser-webpack-plugin
	minimizer:[
               
                new OptimizeCssAssetsPlugin(),
                new TerserWebpackPlugin()

            ],

	},
  1. terser-webpack-plugin 压缩js代码的 看28