webpack是什么
webpack是一个打包工具,可以将多个模块打包成一个或多个bundle。
webpack的工作流程
① 在package.json可以看到执行npm run build时其实是执行webpack cli的命令,webpack cli会去读取该命令带的参数和读取webpack的配置文件然后合并生成一个options的配置对象。读取命令带的参数使用yargs这个模块,例如yargs.argv。没指定配置文件的话默认会去读webpack.config.js这个文件。
② 载入webpack核心模块,创建compiler对象。let compiler = webpack(options),创建compiler对象时会先去注册配置的plugin,这样后面触发到webpack的生命周期钩子时才能回调到这些plugin。
③ compiler对象编译项目,执行compiler对象的run方法,开始编译整个项目。根据entry配置找到入口模块,开始依次递归出所有依赖,形成依赖关系树,然后将递归到的每个模块交给不同的loader处理。
Compilation对象的buidModule方法对模块进行构建,不同的资源使用不同的loader或多个loader进行处理转换,每个资源的最后一个loader输出的是js代码。最后合并生成bundle.js写入dist目录。
webpack的四个核心概念
1.entry 打包入口
module.exports = {
entry: './path/to/my/entry/file.js'
};
你也可以配置多个入口,在打包多页面应用时这种做法就很有用 `
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
下面是一篇打包多页面应用的文章
webpack多入口文件页面打包配置 - 掘金 (juejin.cn)
2.output 输出
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: "http://cdn.example.com/assets/[hash]/"
filename: 'my-first-webpack.bundle.js',
}
当静态资源托管到cdn或者是对象存储如阿里的oss时,就需要配值pulicPath,默认不配置的话是空串。
静态资源最终访问路径 = output.publicPath + 资源loader或插件等配置路径,可以看打包后index.html引入其他资源时的地址查看是否配置成功。
关于开发环境和生产环境如何使用publicpath。可以判断当前环境,对应使用不同的路径。比如开发环境就用./dist 生产环境就用cdn地址。
publicPath: process.env.NODE_ENV === 'production'? config.build.assetsPublicPath : config.dev.assetsPublicPath
3.loader
loader可以处理非js文件,将它们转换成webpack能够处理的模块,接着就可以利用webpack的打包能力对其进行打包了。 `
module: {
rules: [
//意思是在 `require()`/`import` 语句中被解析为 '.txt' 的路径时,在你对它打包之前,先使用`raw-loader` 转换一下
{ test: /.txt$/, use: 'raw-loader' }
]
}
loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。
一个简单的loader其实就是一个函数,对某种文件使用多个loader其实就是多个函数链式调用。每个函数的参数就是上个loader对资源处理后返回的结果,函数体就是当前loader要做的操作,函数返回值就是当前loader对上个loader传递过来的资源处理后的结果。第一个loader的参数就是你要处理的源文件,最后一个loader的返回值要是js代码。
官网有个例子:对一个text.txt文件里的[name],用loader转成你要替换的文字
module: {
rules: [{
//当你import text.txt 时就会先用这个loader对它进行处理
test: /.txt$/,
use: {
loader: path.resolve(__dirname, '../src/loader.js'),
options: {
name: 'Alice'
}
}
}]
}
src/loader.js
//可以在你的loader里使用loader的工具函数,getOptions可以获取你使用该loader时配置的option参数,例如上面的 name: 'Alice'
import { getOptions } from 'loader-utils';
export default function loader(source) {
const options = getOptions(this);
source = source.replace(/[name]/g, options.name);
return `export default ${ JSON.stringify(source) }`;
};
4.plugin
loader被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。有点抽象,后面看看有没有案例解释下。
想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。
`
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
下面是我之前写的plugin,目的是为了每次改完项目不用手动部署到服务器,执行npm run build后就会回调相应的钩子帮我自动部署,当然项目部署不该这么做的。
const { NodeSSH } = require('node-ssh')
class AutoUploadPlugin {
constructor() {
this.ssh = new NodeSSH()
}
async connectServer() {
await this.ssh.connect({
host: "*******",
username: "root",
password: "*****"
})
}
async uploadFiles(localPath, remotePath) {
const status = await this.ssh.putDirectory(localPath, remotePath, {
recursive: true,
concurrency: 10
})
console.log("传送到服务器", status ? "成功" : "失败")
}
apply(compiler) {
compiler.hooks.afterEmit.tapAsync("AutoUploadPlugin", async (compilation, callback) => {
//获取输出的文件夹
const outputPath = compilation.outputOptions.path;
//链接远程服务器
await this.connectServer()
//删除原来目录中的内容
const serverDir = "/usr/share/nginx/vue/dist"
await this.ssh.execCommand(`rm -rf ${serverDir}/*`)
//传送到服务器
await this.uploadFiles(outputPath, serverDir)
//关闭ssh
this.ssh.dispose()
})
}
}
module.exports = AutoUploadPlugin