# part2-module2 **Repository Path**: ren_wei_1/part2-module2 ## Basic Information - **Project Name**: part2-module2 - **Description**: webpack作业 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 0 - **Created**: 2020-08-16 - **Last Updated**: 2021-12-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Webpack作业 ### 1、Webpack 的构建流程主要有哪些环节?如果可以请尽可能详尽的描述 Webpack 打包的整个过程。 1. 初始化参数,即通过命令行(或者配置文件)中的参数进行合并,得到最终打包命令的最后参数 2. 开始编译:通过前面的参数初始化一个Compiler对象,并加载我们所需要的所有的loader,并执行Compiler对象的run方法来执行编译过程 3. 确定入口:通过webpack.config.js中的entry(或者我们自己在命令行中指定),找到我们的入口文件 4. 编译模块:通过我们刚才的入口文件进行递归,找到每个模块的依赖,并调用我们指定的loader进行编译,直到所有依赖都被编译完成 5. 完成模块编译:得到了每个模块最后编译的结果已经他们之间的依赖关系 6. 输出资源:这一步是把我们刚才得到的文件通过他们之间的依赖关系来组成一个一个的chunk,再把每个chunk输出到文件上(这也是最后可以对输出内容进行更改的地方) 7. 输出完成:根据配置确定输出的路径和文件名,把文件内容写入到文件中 > 在上面的过程中,webpack会在特定的时间广播特定的事件,插件会监听这些事件,并在监听到的事件中执行自己的逻辑,然后调用webpack提供的api来把修改过的内容返回回去 ### 2、Loader 和 Plugin 有哪些不同?请描述一下开发 Loader 和 Plugin 的思路。 - Loder > loder在webpack中提供的是编译和转换代码的功能 - Plugin > Plugin是用于完成loader无法完成的事情,通常都是通过监听webpack的生命周期,然后执行自己的逻辑,并将最后的结果通过调用webpack的api返回回去 #### 2.1、Loader开发 由于webpack是是运行在nodejs上的,所以loader其实就是一个Node.js模块,这个模块需要导出一个函数,这个函数的作用就是获得处理前的内容,对内容进行转换处理,并把处理后的内容返回回去。代码类似下面这样 ``` module.exports=(source)=>{ // 做一些处理操作,然后把处理完成之后结果返回回去 // 当然在这个里面,我们也是可以引入第三方模块来进行处理,方法就和平时的nodejs写法相同 const result = source; return result; } ``` #### 2.2、Plugin开发 由于webpack工作中有很多环节,并且webpack给每个环节都埋下了钩子(个人感觉也可以理解成生命周期),我们开发插件的时候只需要通过这些钩子来触发相应的任务就可以了。webpack中也规定了,plugin必须是一个函数或者一个包含apply的对象。一个简单的插件实现,如下所示 ``` class MyPlugin{ // 传入相应的配置信息 constructor(options){ } // 将Compiler对象传递进来 // 然后通过这个对象来注册钩子函数 apply(compiler){ // 注册emit钩子方法 // compilation是一个上下文, compiler.hooks.emit.tap("MyPlugin",compilation=>{ //在这里执行我们的方法来修改内容 }) } } ``` ### 3.webpack配置vue开发的流程 - 为了实现webpack 的打包命令,首先我们需要安装webpack webpack-cli到我们的开发依赖中,然后编写我们的module.common.js文件,这里面是我们的共用的打包命令,代码如下: ``` const path = require("path"); //公共的打包配置 module.exports = { //入口文件 entry: "./src/main.js", //打包出口文件 这个里面我们需要用到__dirname来生成一个绝对路径 output: { path: path.resolve(__dirname, "dist") } }; ``` - 然后我们运行 yarn webpack --config webpack.common.js的命令,我们会发现报错了,里面报错基本都是因为我们没有添加相应文件的loader造成的,所以接下来我们需要引入相应的loader,首先因为我们这个项目用的是vue,所以我们需要安装vue-loader的依赖来对vue文件进行解析和转换,代码如下 ``` module: { rules: [ { test: /\.vue$/, //用于解析vue文件 use: ["vue-loader"] } ] }, ``` - 接下来,由于我们项目中使用的是less,所以我们需要安装less-loader来对less进行解析和编译,同时,我们还需要安装css-loader和style-loader来对css文件和vue中的style标签进行解析,代码如下 ``` { test: /\.less$/, //对于less代码,我们需要使用less-loader来转换,然后再用css-loader 和 vue-style-loader use: ["style-loader","css-loader", "less-loader"] }, { test: /\.css$/, //这个是用来转换vue中的style标签和css文件的 use: ["style-loader","css-loader"] }, ``` - 接下来是对js文件的解析,这里我们需要使用到babel-loader 和@vue/cli-plugin-babel,这里面我们不仅需要在webpack的配置文件中写入我们的内容,我们还需要在根目录新建一个babel.config.js,具体内容如下 ``` //webpach中的内容 { test: /\.js$/, //刨除node_modules中的js代码 exclude: /node_modules/, //对于js代码,我们需要使用babel-loader来进行代码的转换编译 use: ["babel-loader"] }, //babel.config.js中的内容 module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ] } ``` - 再然后是对图片文件的处理,我们使用url-loader来处理。由于url-loader是基于file-loader的,所以我们需要同时安装这两个依赖,代码如下 ``` { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //对于文件的复制 loader: "url-loader", //限制最大为10k 超过10K还是按文件存储,不会转换为base64 options: { limit: 10 * 1024, //一定要加这个 不然src中会是一个object module esModule: false } } ``` - 后面我们直接运行前面的命令来进行打包,然后我们会发现报错了,报错信息提示我们需要使用一个VueLoaderPlugin插件,这个插件在vue-loader中可以找到,所以我们直接引入,然后写在plugins中即可 ``` const VueLoaderPlugin = require("vue-loader/lib/plugin"); plugins: [new VueLoaderPlugin()] ``` - 但是此时我们发现没有html的解析文件,如果所以我们需要安装一个htm-webpack-plugin,用于指定html模板来生成我们的index.html,由于过程BASE_URL会报错所以我们需要使用webpack的DefinePlugin来定义一下BASE_URL的位置 ``` plugins: [ .... //添加我们的HtmlWebpackPlugin Ï new HtmlWebpackPlugin({ template: "./public/index.html", }), //后续会把这个抽离到webpack.dev.js中 new DefinePlugin({ //定义BASE_URL index.html中需要使用 BASE_URL: "/public/" }) ] ``` - 整个common的打包配置我们写的差不多了,接下来我们来实现开发环境下的打包,由于开发环境下,我们需要使用webpack-dev-server来启动,所以我们需要安装webpack-dev-server,同时,我们还需要安装一个webpack-merge来合并配置,同时由于是开发环境,所以我们需要生成对应的sourceMap来方便进行代码错误的查找 ``` const path = require("path"); const VueLoaderPlugin = require("vue-loader/lib/plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); //公共的打包配置 module.exports = { //入口文件 entry: "./src/main.js", //打包出口文件 这个里面我们需要用到__dirname来生成一个绝对路径 output: { path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.vue$/, //用于解析vue文件 use: ["vue-loader"] }, { test: /\.js$/, //刨除node_modules中的js代码 exclude: /node_modules/, //对于js代码,我们需要使用babel-loader来进行代码的转换编译 use: ["babel-loader"] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //对于文件的复制 loader: "url-loader", //限制最大为10k 超过10K还是按文件存储,不会转换为base64 options: { limit: 10 * 1024, //一定要加这个 不然src中会是一个object module esModule: false } } ] }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: "./public/index.html" }) ] }; ``` - 当配置完成之后,我们通过 **yarn webpack-dev-server --config webpack.dev.js --open**命令即可启动浏览器,并看到我们的页面。接下来就是我们生产环境的配置,生产环境下,我们需要关闭sourceMap来防止源代码外泄,同时我们需要用hash值来命名我们的文件名,这样就可以防止浏览器缓存,导致代码或者样式没有更新的情况 ``` const { merge } = require("webpack-merge"); const commonConfig = require("./webpack.common"); const { DefinePlugin } = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); //生产环境的配置 module.exports = merge(commonConfig, { mode: "production", //去除sourceMap devtool: "none", //输出的文件名 output: { filename: "[name].[chunkHash:7].js" }, plugins: [ new HtmlWebpackPlugin({ tempalte:"./src/index.html", favicon: "./public/favicon.ico", // 在此处设置 //对html代码进行压缩 minify: { removeComments: true, //去注释 collapseWhitespace: true, //压缩空格 removeAttributeQuotes: true //去除属性引用 } }), new DefinePlugin({ BASE_URL: process.env.NODE_ENV }), //用于每次生成的时候,清理上次的打包文件 new CleanWebpackPlugin() ] }); ``` - 经过这一步,我们所有的代码都打包到了一个js文件中,接下来,我们首先把css样式文件分离出来,这一步我们需要使用到mini-css-extract-plugin,并且我们需要更改一下less和css文件的loader ``` module: { rules: [ { test: /\.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"] }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "css/[name].[hash:7].css", chunkname: path.posix.join("static", "css/[id].[chunkhash:7].css") }) ], ``` - 接下来,我们需要做的就是对css文件和js文件的压缩和分包,并且通过webpack来去掉代码中的dubugger,console等,这一步我们需要使用optimize-css-assets-webpack-plugin 和 terser-webpack-plugin ``` optimization: { //代码分包 splitChunks: { chunks: "all" }, minimize: true, minimizer: [ //css压缩 new OptimizeCSSAssetsPlugin({ cssProcessorOptions: { discardComments: { remove: true } //移除注释 } }), new TerserPlugin({ parallel: true, //开启多线程来提高构建速度 sourceMap: false, terserOptions: { warnings: false, //不展示warning compress: { unused: true, //去除未使用的 drop_debugger: true, //移除debugger drop_console: true //去除console }, output: { comments: false //去除注释 } } }) ] } ``` - 然后我们执行yarn webpack --config webpack.prod.js 来进行打包,然后再用浏览器打开dist目录下的index.html就可以看到我们的打包结果了 - 由于命令行的长度太长了,所以我们可以吧命令直接使用npm script的方式来执行,具体就是修改package.json里面的东西 ``` "scripts": { "serve": "webpack-dev-server --config webpack.dev.js --open", "build": "webpack --config webpack.prod.js", "lint": "eslint ./src" }, ``` #### 下面是全部的代码 - webpack.common.js ``` // webpack.common.js const path = require("path"); const VueLoaderPlugin = require("vue-loader/lib/plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); //公共的打包配置 module.exports = { //入口文件 entry: "./src/main.js", //打包出口文件 这个里面我们需要用到__dirname来生成一个绝对路径 output: { path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.(vue|js)$/, loader: "eslint-loader", exclude: /node_modules/, // 预处理 enforce: "pre" }, { test: /\.vue$/, //用于解析vue文件 use: ["vue-loader"] }, { test: /\.js$/, //刨除node_modules中的js代码 exclude: /node_modules/, //对于js代码,我们需要使用babel-loader来进行代码的转换编译 use: ["babel-loader"] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //对于文件的复制 loader: "url-loader", //限制最大为10k 超过10K还是按文件存储,不会转换为base64 options: { limit: 10 * 1024, //一定要加这个 不然src中会是一个object module esModule: false } } ] }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: "./public/index.html" }) ] }; ``` - webpack.dev.js ``` // webpack.dev.js Ïconst commonConfig = require("./webpack.common"); const { DefinePlugin } = require("webpack"); const { merge } = require("webpack-merge"); module.exports = merge(commonConfig, { //开发环境 mode: "development", //指定sourceMap devtool: "#@cheap-module-source-map", module: { rules: [ { test: /\.less$/, //对于less代码,我们需要使用less-loader来转换,然后再用css-loader 和 vue-style-loader use: ["style-loader", "css-loader", "less-loader"] }, { test: /\.css$/, //这个是用来转换vue中的style标签和css文件的 use: ["style-loader", "css-loader"] } ] }, plugins: [ new DefinePlugin({ //定义BASE_URL index.html中需要使用 BASE_URL: "/public/" }) ] }); ``` - webpack.prod.js ``` // webpack.prod.js const { merge } = require("webpack-merge"); const commonConfig = require("./webpack.common"); const { DefinePlugin, LoaderOptionsPlugin } = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const path = require("path"); //生产环境的配置 module.exports = merge(commonConfig, { mode: "production", //去除sourceMap devtool: "none", //输出的文件名 output: { filename: "js/[name].[hash:8].js", publicPath: "./" }, //更改css和less的loader module: { rules: [ { test: /\.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"] }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] } ] }, plugins: [ new HtmlWebpackPlugin({ template: "./public/index.html", favicon: "./public/favicon.ico", // 在此处设置 //对html代码进行压缩 minify: { removeComments: true, //去注释 collapseWhitespace: true, //压缩空格 removeAttributeQuotes: true //去除属性引用 } }), new DefinePlugin({ BASE_URL: process.env.NODE_ENV }), //用于每次生成的时候,清理上次的打包文件 new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: "css/[name].[hash:7].css", chunkname: path.posix.join("static", "css/[id].[chunkhash:7].css") }) ], optimization: { //代码分包 splitChunks: { chunks: "all" }, minimize: true, minimizer: [ //css压缩 new OptimizeCSSAssetsPlugin({ cssProcessorOptions: { discardComments: { remove: true } //移除注释 } }), new TerserPlugin({ parallel: true, //开启多线程来提高构建速度 sourceMap: false, terserOptions: { warnings: false, //不展示warning compress: { unused: true, //去除未使用的 drop_debugger: true, //移除debugger drop_console: true //去除console }, output: { comments: false //去除注释 } } }) ] } }); ```