webpack 4.x 快速上手

安装

yarn add webpack webpack-cli -D

配置项

  • 真正应用项目的webpack往往需要根据自己的业务来进行修改默认的配置,而不是一股脑的使用webpack默认的配置。这个时候就需要我们去了解webpack常用的配置项含义.
  • 配置项所在的文件是webpack的配置文件,其运行在node的环境中,所以需要使用CommonJS规范来进行配置文件的编写。

mode

指定webpack的工作模式,生产环境还是开发环境还是最原始的环境。值为development 或者 production 或者none

const path = require('path');
module.exports = {
    mode: 'development', //指定webpack的工作模式
    entry: path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.[hash].js',
        path: path.join(__dirname, 'output')
    }
}

entry

指定webpack打包的入口js文件。可以使用node提供的path模块来进行入口文件的路径指定,也可直接用相对路径的方式来进行入口文件路径的指定(注意采用相对路径的方式时,要将完整的路径写全,尤其是./不能缺少)

module.exports = {
    entry: './src/main.js',  // 相对路径方式
    entry: path.join(__dirname, 'src/index.js'), // path模块指定路径方式
}

output

指定webpack打包完成之后,输出文件的路径位置以及输出文件名称。默认输出路径是根目录下的dist文件夹。可采用path模块路径的方式

const path = require('path');
module.exports = {
    // entry: './src/index.js',
    entry: path.join(__dirname, 'src/index.js'),
    output: {
        filename:'bundle.[hash].js', // 输出文件的名称
        path:path.join(__dirname,'output'), // 输出文件的路径
        publicPath: "",// 默认是空字符串,意思是网站的根目录
    }
}

module

  • 意思就是遇到的模块化代码都会走这个配置项进行处理。
  • 值是一个对象,第一层键值对是rules,rules是规则,意思就是针对其他资源加载的规则配置,一个项目会有多种类型的文件,所以会有多个规则。所以rules是一个数组。
  • 数组中的元素都是对象类型,对象类型必须设置两个属性,一个是test,一个是use。
  • test值是一个正则表达式用于去匹配webpack打包过程中遇到的资源文件的路径。
  • use属性值是指定要处理test匹配的类型文件专用的loader(加载器)
  • use的值可以是一个数组,当处理同一类型的文件需要多个loader的时候,它就是一个数组。loader数组的执行顺序是从后往前的执行。
const path = require('path');
module.exports = {
    mode: 'none', //指定webpack的工作模式
    entry: path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.[hash].js',
    },
    module: {
        // 针对其他资源加载规则的配置,所以是一个数组
        rules: [
            {
                test: /\.css$/,  // 匹配到所有的css文件
                use: 'css-loader' // 采用css-loader进行文件处理
            }
        ]
    }
}

loader

  • 加载器也叫转换器,用于处理源代码中任何类型的资源。
  • webpack的内部默认loader只能处理js文件,如果想处理js以外的类型文件,例如:css文件、图片文件等。就需要特定的loader来对文件进行转换。
  • loader工作过程是接收原始资源数据作为参数经过加载器loader的处理,最终输出能够让webpack打包的JavaScript代码。
  • loader分类:1、编译转换类的loader(将模块资源转换成js模块代码)2、文件操作类型的loader;(将源静态资源文件经过压缩后拷贝,然后导出静态资源文件的访问路径);3、代码检查类型的loader(检查代码的风格)
css-loader
style-loader

将css-loader转换得到的js模块代码,再次转换其结果最终以style标签的形式将样式挂载在html文件中的head标签内

file-loader
url-loader
  • 将静态文件转换成代码的形式(Date URLs),没有物理文件的输出,就减少一次http请求。
  • 小文件使用Data URLs的形式,减少http的请求次数
  • 大文件单独提取存放,提高打包之后的js代码的加载速度
  • url-loader在输出独立的大文件的时候,需要借助file-loader去输出
babel-loader

用于转换es6+的特性,同时需要babel的核心模块@babel/core与转换具体特性的集合插件@babel/preset-env

自己开发loader

  • loader 其实就是一个函数,以原始资源为参数,以转换的资源结果为输出。所以loader的本质就是对原始资源的处理过程
    在这里插入图片描述
  • 简单处理md文件的loader处理
const marked = require('marked');
// source 就是原始资源
module.exports = (source) => {
	// 对source的原始资源进行处理
    const html = marked(source);
    // 将处理的结果以js代码的形式导出
    return `module.exports = ${JSON.stringify(html)}`
}

plugins

插件是webpack的另一个核心特性,其作用是用于增强webpack的自动化能力。是实现前端工程化的重要组成部分
例如:

  • 依靠插件清除打包产生的dist目录
  • 依靠插件直接将public文件夹拷贝至输出目录
  • 依靠插件对loader转换之后的代码进行压缩,去除代码中的注释,debugger等等

clean-webpack-plugin

用于自动清除输出目录的插件

html-webpack-plugin

自动生成打包出来的js所用到的html插件,如果需要同时输出多个页面文件,那么就配置多个html-webpack-plugin的实例即可。

copy-webpack-plugin

  • 将public文件夹的文件完全复制到输出目录中
  • 该插件最好是只在生产模式下启用,目的是为了提高开发模式下的编译速度
{
	 plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './public/index.html',
            publicPath: './', // html中引入的文件路径
        }),
        new CopyWebpackPlugin([
            'public'
        ])
    ],
}

mini-css-extract-plugin

提取css到单个文件中,实现css的按需加载。其中使用MiniCssExtractPlugin.loader取代style-loader

optimize-css-assets-webpack-plugin

压缩提取出来的css文件,webpack内置的压缩插件(terser-webpack-plugin)仅仅只对js文件进行压缩。

  • 注意:webpack推荐将所有压缩类的插件全部放在optimization配置项中的minimizer进行管理。直接放在plugins里面也是可以的
optimization:{
        minimizer:[
            new OptimizeCssAssetsWebpackPlugin(),
            new TerserWebpackPlugin()
        ]
    }

自己开发webpack插件

  • 本质:在生命周期的钩子函数中挂载函数来实现扩展
  • plugin必须是一个函数或者一个包含apply方法的对象。
  • 以下是一个清除注释的自定义插件
class MyPlugin {
    // webpack 启东时会自动调用apply方法,compiler参数会暴露生命周期钩子函数
    apply(compiler) {
        console.log('自定义插件启动了')
        // 通过tap方法注册钩子函数
        compiler.hooks.emit.tap('MyPlugin', compilation => {
            // compilation  看成webpack打包的上下文
            for (let name in compilation.assets) {
                if (name.endsWith('.js')) {
                    // 拿到name对应的值
                    const content = compilation.assets[name].source();
                    const withoutAnnoation = content.replace(/\/\*\*+\*\*\//g, '')
                    compilation.assets[name] = {
                        source: () => withoutAnnoation,
                        size: () => withoutAnnoation.length
                    }
                }
            }
        })
    }
}

devServer

通过devServer参数来增加webpack的开发体验

自动编译

在webpack命令行启动的时候添加–watch参数 用于监听文件的变动,一旦文件发生变动,则会自动重新进行编译

yarn webpack --watch

webpack dev server

根据名字即可得知它提供了一个临时web服务的功能。同时集成了自动编译的功能

  • 安装
yarn add webpack-dev-server -D

注意在安装的过程中可能会出现webpack webpack-cli 版本太高导致错误。根据错误网上搜索解决方案即可。

  • webpack-dev-server 为了提高效率输出内容并没有写入磁盘,而是写入到内存当中。

  • 通过dev-server提供的代理API参数(proxy)可解决开发模式下的跨域问题

{
	proxy: {
            // 请求以/api开头的url,都会代理到配置的target接口中
            // http://localhost:8000/api/users --→ https://api.github.com/api/users
            '/api': {
                target: 'https://api.github.com',
                // 对请求的url进行重写
                // http://localhost:8000/api/users --→ https://api.github.com/users
                pathRewrite:{
                    '^/api':''
                },
                changeOrigin:true, // 不能使用localhost:8000作为主机名,所以要设置成true
            }
        }
}
HMR
  • 产生背景:当webpack监听到文件中的代码发生变动的时候,webpack会重新打包编译,此时会造成浏览器的重新刷新,界面之前保存的任何状态会全部丢失。这种开发体验非常不好
  • 作用:解决自动刷新导致的页面状态丢失
  • 配置开启HMR的方式
  1. devServer中的hot设为true
  2. 引入webpack身上的HotModuleReplacementPlugin插件,放在plugins中使用

遇到的问题:只是这样配置的话可以实现更改css浏览器保留状态热更新的效果,但是更改js或者图片等模块的时候,浏览器仍然是会重新刷新,丢失状态。这并不是HMR不行,而是因为webpack需要我们自己手动处理模块热替换的逻辑。之所以CSS可以直接实现模块热替换的效果是因为在style-loader源代码里面已经进行了对样式的热替换处理

  • HMR APIs
    在入口文件利用HMR的api来对不同模块进行热替换处理。
  1. module.hot.accept ----- 注册某一个模块更新之后的处理函数
// accept接收两个参数,第一个参数是要进行热更新的js文件(填写完整的相对路径),第二个参数是目标js文件更新之后的处理函数。
module.hot.accept('./head',()=>{
    console.log('head 模块更新了  要对其进行热替换处理')
})

问题:如果不用框架,使用原生js的话,那么HMR的使用将会变得异常麻烦,因为你需要根据不同的逻辑去编写不同的热更新处理代码,毫无疑问它会额外的增加开发成本。

  • react框架 额外的处理方案
  1. webpack 5 已经实现了0配置,模块热替换。
  2. webpack 4 可以通过react-refresh插件来实现

完整dev-server 案例

devServer: {
        contentBase: ['./public'],  //静态资源的访问,其作用与copy-webpack-plugin作用一样,使用了contentBase以后开发模式下就可以不用CopyWebpackPlugin插件了,提高编译速度
        open: true, // 自动打开浏览器
        port: 8000, // 指定端口号
        // hot: true, // 开启HMR,当代码出现错误的时候,会重新刷新浏览器,导致看不到代码错误信息
        hotOnly:true, //  // 开启HMR,当代码出现错误的时候,不会重新刷新浏览器,控制台可以查看代码错误信息
        proxy: {
            // 请求以/api开头的url,都会代理到配置的target接口中
            // http://localhost:8000/api/users --→ https://api.github.com/api/users
            '/api': {
                target: 'https://api.github.com',
                // 对请求的url进行重写
                // http://localhost:8000/api/users --→ https://api.github.com/users
                pathRewrite: {
                    '^/api': ''
                },
                changeOrigin: true, // 不能使用localhost:8000作为主机名,所以要设置成true
            }
        }

    }

devtool

Source Map的介绍

  • 中文译为源代码地图,映射了源代码与编译转换之后的代码之间的关系
  • 用于解决源代码与生产环境下代码不一致,导致生产环境下代码出现错误不能准确定位的问题

配置source map

{
	devtool:'source-map', 
}

devtool的值

devtool的值决定了以哪种方式进行源代码的映射,每种方式的效率不同。映射效果越好,效率越低。
在这里插入图片描述
表格中build意思是初次构件的速度,rebuild意思是在watch模式下的重新构建速度,production意思是是否适合生产模式,quality意思是生成的source的质量。

  • 生产模式下推荐 none
    source map会暴露源代码,安全隐患较大
  • 开发环境下推荐 cheap-module-eval-source-map
    由于在使用react和vue的框架下,正常都会使用代码风格工具,所以错误只需要定位到行即可

webpack 优化

默认情况下 production模式下的打包是有一些默认的优化配置的,同时我们还可以自行去配置优化。

webpack DefinePlugin

为代码注入全局常量,这样在代码中可以不用引入而直接使用注入的全局常量。

new webpack.DefinePlugin({
     API_URL: JSON.stringify("http://api.example.com")
 })

Tree-shaking

  • 从中式翻译上来看就是摇树的意思。生活中摇树会将树上的残枝落叶(残枝落叶已经从树身上脱离,有没有这些树都可活下来)摇下来。同样的,对于代码来说,一个项目中肯定有未引用的代码,通过Tree-shaking将这些未引用的代码去除掉,减少了打包之后的代码体积,为代码进行瘦身。
  • Tree-shaking并不是配置文件中的某一个配置项,它是一组功能搭配使用后的优化效果
  • 在production模式下会自动开启这个功能
  • 其他模式手动启用tree-shaking的话,需要借助webpack中的optimization这个配置项
optimization: {
        usedExports: true, // 标记代码中未被使用的代码
        minimize: true, // 压缩并清除被被使用的代码
        concatenateModules:true // 合并多个模块至一个函数中
    },

以摇树为比喻:开启usedExports用来标记树上的残枝落叶,开启minimize会把残枝落叶从树上摇下来。

  • concatenateModules属性
    正常打包的过程中会将一个模块打包成一个函数,如果代码中有多个模块就会打包出多个函数。开启concatenateModules意思就是尽可能的将所有模块合并输出到一个函数中。从而来减少代码的体积,提高了代码的运行效率。

tree-shaking & babel-loader

两者同时使用的时候会造成tree-shaking失效。

  • 原因:tree-shaking的前提就是ES Module,而babel-loader在转译ES的新特性的时候,可能会将ES Module 转换成CommonJS。例如使用babel-loader的时候还会使用@babel/preset-env插件,该插件就会将ES Module 转换成 CommonJS 。无需担心,该问题已经在新版的babel-loader已经修复掉。
  • 对于旧版本的@babel/preset-env插件我们可以通过配置参数来禁止ES Module 转换成 CommonJS
{
presets: [
          [require('@babel/preset-env'), {modules: false}] // modules设为false,意思是禁止ES Module转换,保证tree-shaking生效
         ]
}

webpack sideEffects

  • 作用:标识代码是否有副作用 为代码提升可压缩的空间。

  • 副作用:指的是模块执行时除了导出成员之外还做的其他事情

  • 应用场景:一般在开发npm包的时候会用到该属性

  • 在production模式下sideEffects会自动开启。

  • 使用:在package.json文件中sideEffects属性设为false,意思是项目整体代码没有副作用,那么webpack在打包的时候会将没有用到的代码全部移除掉。如果sideEffects属性值是一个数组,那么数组中的内容就是副作用代码,webpack在打包的时候不会将数组中的文件删掉。

  • 副作用代码案例:
    案例一:
    在这里插入图片描述
    案例二:
    在react项目中,jsx文件中通过import引入的css代码,全都属于副作用代码

  • 注意事项
    在optimization里面的sideEffects属性指的是webpack开启了sideEffects功能,而package.json文件中的sideEffects字段意思是标识整体项目是没有副作用的。

Code Splitting

  • 产生背景:webpack在打包的时候会把所有代码打包在一起,造成bundle体积非常大。而应用在启动的时候,并不是所有的模块都要加载才能启动。这样会造成首次加载时间过长。这个时候按需加载应运而生。
  • 实现方式:多入口打包 & 动态导入

多入口打包

  • 一个页面对应一个入口文件,提取多个页面公共部分。此方式适合多页面应用打包
  • 具体实现
  1. webpack的entry参数值是一个对象,该对象中一个属性就是一个入口,属性的key就是入口名称,属性值是入口文件的路径。
  2. 有多个入口文件就有多个输出文件。那么此时还要配置output属性值采用占位符的形式。
  3. 多入口文件肯定也会有多个html文件,此时plugins配置项中的HtmlWebpackPlugin也要配置多个
  4. 多入口文件有时会同时引用相同的文件,那么相同的文件就是公共模块,如果不提取,就会被打包两次。需要将公共部分单独打包到一个bundle中,供所有html文件引用。在optimization配置项中开启splitChunks功能即可实现提取公共部分
{
	entry: {
        index: path.join(__dirname, 'src/index.js'), // 第一个入口文件
        about: path.join(__dirname, 'src/about.js') // 第二个入口文件
    },
    output: {
        filename: '[name].bundle.js', // name 就是入口文件的名称
    },
    plugins: [
		new HtmlWebpackPlugin({
            template: './public/index.html',
            publicPath: './', // html中引入的文件路径
            filename: "index.html", // 输出的文件名
            chunks: ['index'], // 指定html文件所引入的打包完成的js 模块
        }),
        new HtmlWebpackPlugin({
            template: './public/about.html',
            publicPath: './', // html中引入的文件路径
            filename: "about.html", // 输出的文件名
            chunks: ['about']  // 指定html文件所引入的打包完成的js 模块
        }),
	],
	optimization: {
		splitChunks: {
            chunks: "all", // 将多入口文件中所有的公共模块都单独提取到一个bundle之中
        }
	}
}

动态导入

  • 在需要用到某个模块的时候才去加载对应的模块,适合SPA应用
  • 动态导入的模块都会被webpack提取到一个单独的bundle中
  • 实现方式:通过import函数来实现模块的动态导入,代替以往在js文件头部一次性将所有的模块引入的方式
// import {test} from "./foot";

// import函数接收一个要进行动态导入模块的路径作为参数,返回一个promise对象
import('./foot').then(module => {
    console.log(module) // module 就是 foot.js文件导出的内容
})
  • 对于react和vue这种spa框架,在路由配置的地方就应该采用动态导入的方式实现按需加载
魔法注释
  • 产生背景:通过动态导入的模块的名称就是一个序号,如果想重新命名,可以通过魔法注释的方式实现
  • 实现方式:在import函数的参数位置添加行内注释
import(/* webpackChunkName: 'head' */'./head').then(({createEle}) => {
    const dom = createEle();
    document.body.appendChild(dom)
})

输出带有Hash的文件名

生产模式下推荐使用hash名,前后两次部署的前端静态文件,一旦静态文件的hash发生变化才去重新请求,若静态文件的hash没变化,则直接从缓存中取。

  • [hash]
    项目级的hash,一旦项目中的任何文件变动了,所有的 [hash]都会变化
  • [chunkhash]
    同一路的hash
  • [contenthash]
    文件级别的hash,精确的去控制不同文件的hash值,推荐使用
  • hash长度的指定
    [contenthash:8]

webpack配置文件导出一个函数

module.exports = (env,argv)=> {
	// env 是命令行中的环境参数
	// argv 是命令行中的所有参数
	// 根据env的不同可以修改config的具体配置项参数
	const config = {
		...webpack的参数
	}
	return config;
}

一个环境一个配置文件

  • 此种方式适合大型项目。
  • 一共需要三个文件,一个development环境配置文件,一个production环境配置文件,一个提取development与production 的公共配置文件。
  • 使用webpack-merge进行配置的合并

webpack 工作原理

webpack会从配置文件中所配置的入口js文件开始解析打包代码, 根据入口文件中的import或require语句解析入口文件所依赖的资源模块,形成一个资源依赖树,webpack会递归这个依赖树找到每一个树节点所依赖的资源文件,再根据配置文件中的rules规则去找到模块对应的loader,通过loader去转换依赖的资源文件,loader转换的结果会放到bundle中。所以loader是webpack的核心。

webpack打包生成的js的运行原理

webpack打包完输出的js文件中是一个立即执行函数,该函数接收modules作为参数。该函数自调用的时候会传一个数组,数组中是相同的函数,每一个函数对应源代码中的module,以此来形成module的私有作用域。
注意:需要自己手动在浏览器进行代码的调试

命令行输入webpack执行了什么

当在命令行里面键入webpack的命令,按下Enter键之后,首先会到node_modules下面的.bin目录寻找webpack文件,该文件中代码量总共为149行。其中关键点就是在最后会调用其中的runCLi函数,根据函数内部的逻辑,可以得到紧接着又去找node_modules下面的webpack-cli文件夹下面bin目录中的cli.js文件。接下来我们要做的就是调试源代码。所谓的调试,其实就是依据代码中的函数的命名在结合实际webpack的执行结果,来推测源代码中到底在做什么。总结一句话就是,看函数,找调用,忽略判断条件,只去猜大体的执行意思。

对于cli.js文件来说一般只做两件事:1、处理options参数。2、将参数向后面的业务逻辑进行传递

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值