webpack4.0 把自己总结'吐'的一篇文章

2,031 阅读8分钟

上大学的时候老师讲一门语言,上来的第一节课就是配置各种环境,而通常看到那些脑袋都是一种快要炸的状态🤯。随着后来对Linux的学习之后发现,并没有那么头疼配置的东西(对于命令这么复杂的东西都可以接受,那么配置是不是也不显得那么枯燥了😂)。

本章是结合自己项目中的应该以及查看了webpack官方文档之后总结的一篇配置。目的也是锻炼自己在配置方面的兴趣(可能你们不懂恶心配置的那种感觉😩,只能慢慢克服)如有错误,还请各位大佬指出🙏,小女子在此谢过🤪。

话不多说,看正文👇

一、webpack的认识

webpack能够把我们项目中引入的一个或者多个css或者JS资源文件,最终安装指定的依赖能够打包压缩成一个css或者一个JS,以此来实现我们的性能优化。以这种自动化的方法来完成项目的部署和构建,告别了手工。webpack是基于node.js开发的

在项目中的应用

  • 代码转换:TypeScript编译成JavaScript、LESS/SCSS编译成CSS、ES6/7编译为ES5、虚拟DOM编译为真实的DOM等等…
  • 文件优化:压缩JS、CSS、HTML代码,压缩合并图片,图片BASE64等
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码等
  • 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件
  • 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器
  • 代码校验:Eslint代码规范校验和检测、单元测试等
  • 自动发布:自动构建出线上发布代码并传输给发布系统
  • ……

webpack同类型工具

  • grunt
  • gulp
  • fis
  • webpack(Browserify / Parcel [ˈpɑːsl])
  • snowpack

webpack中文网学习

webpack中文网地址

二、前端模块化开发进化史

JavaScript本身是弱化命名空间概念的,只有全局作用域和函数的私有作用域(ES6中新增块作用域),而模块化开发,从某种意义上来说,是强化了命名空间的概念!

  • 有利于代码分离、解耦以及复用
  • 团队并行开发
  • 避免命名冲突
  • 相互引用,按需加载
  • ……

单利设计模式

let xxxModule=(function(){
    function fn(){
        //...
    }
    return {
        init(){
            //...        
        }
    }
})();

xxxModule.init();

AMD (Asynchronous Module Definition 异步模块定义)

如果需要使用外面的模块,需要先引入该模块,但是这种方式在现在的项目中已经不常用

文件目录:

|- lib
   |- moduleA.js
   |- moduleB.js
|- main.js

//main.js
//全局配置
require.config({
    baseUrl: 'js/lib',
});
require(['moduleB', 'moduleA'], function (moduleB, moduleA) {
    console.log(moduleB.average(10, 20, 30, 40, 50));
});
//moduleA.js
define(function () {
    return {
        sum(...args) {
            return args.reduce((total, item) => {
                return total + item;
            });
        }
    };
});
define(['moduleA'], function (moudleA) {
    return {
        average(...args) {
            args.sort((a, b) => a - b);
            args.pop();
            args.shift();
            return moudleA.sum(...args) / args.length;
        }
    };
});

CMD (Common Module Definition 通用模块定义)

define(function(require, exports, module) {
  // 通过 require 引入依赖
  let $ = require('jquery');
  let spinning = require('./spinning');

  // 通过 module.exports 提供接口
  module.exports = {
      init(){}
  };
});

CommonJS(一般应用于服务器开发,例如:Node.js)

  • 导出:module.exports
  • 导入:require
//CommonJS规范的使用
let A = require('./A');
// 第一次REQUIRE A模块,会把A模块中的代码执行,
//创建的变量A就是把模块中module.exports导出的结果拷贝一份给变量
A.sum(x, y);


A = require('./A');
// 第二次REQUIRE A模块,内部默认会看一下之前有没有导入过,如果导入过不会再把A代码重新执行,而是直接获取上一次拷贝的信息

ES6 Module (ESM : JS官方标准模块定义方式)

//ES6模块导入导出的使用
/*
 * ES6Module是JS新增的模块导入导出规范(不同于AMD 和 CMD【CommonJS】,它是静态编译的)
 *    动态编译:代码执行到具体位置的时候才会进行模块的导入导出
 *             (在第十行导入那么就在第十行才进行导入)
 *    静态编译:代码还没有执行,就按照依赖的关系吧模块导入导出和编译好了
 * 模块的导入都要放在执行的最前面
 * 浏览器不能直接识别,需要先进行编译才可以(webpack可以完成这个编译)
*/
//导出
export function sum(x, y) {
 return x + y;
}

export let n = 10;
//导入 2种方式
import {sum,n} form './A.js';
import * as A form './A.js'

三、安装webpack

目前@vue/cli和create-react-app基本上采用的是webpack 4.0以上版本,所以以第四代版本为主;第四代版本需要我们安装webpack和webpack-cli(可执行命令)

// 为防止全局安装webpack导致版本冲突,项目中以本地安装为主 
$ npm init -y  //生成package.json文件
$ npm install webpack webpack-cli --save-dev  //安装开发依赖
// OR
$ yarn add webpack webpack-cli -D  //如果安装过yarn的可以时候这个,速度会快一些

扫盲

  • 生产依赖:服务器部署的环境 --save
  • 开发依赖:本地开发的环境 --save-dev (在本地使用less进行css的编写,但是在项目上传的时候不需要把less上传。因为已经把它生成了.css的文件,如果把less上传上去,每次都会编译,影响性能,因此less就属于一个在开发依赖需要的模块)

四、零基础配置使用webpack

运行命令(以下任何一条都可以)

  • $npx webpack
  • 在package.json文件的script中配置命令,然后按照下面的命令执行
    • npm run serve
    • yarn serve
/*
 * 默认会打包SRC目录中的JS文件(入口默认index.js)
 * 打包完成的目录默认是DIST/MAIN.JS
 * webpack默认支持CommonJS和ES6 Module的模块规范,依此进行依赖打包
 */
$ npx webpack  
运行webpack配置
运行webpack配置

五、自定义基础配置--配置文件名

配置需要写的文件名

如果我们需要去自定义配置一些文件,首先需要知道这些配置需要写在哪里。这里不管写在什么配置文件里,区别就是执行webpack的命令不一样。

webpack默认支持的配置文件名

  • webpack.config.js(这种常用)
  • webpackfile.js 上面两种是webpack默认支持的配置文件名,那么在执行的时候使用npx webpack或者在package.json文件中自己配置可执行的命令(上面刚提到的)。

自定义配置文件名

在项目中有时候需要把我们的配置文件名称分为开发配置和生产配置,如果是这个配置文件名那么在执行webpack的时候应该使用什么命令。

比如现在我把自己的配置项写在webpack.config.dev.js里。

第一种方式:那么需要在上面执行的命令中加入指定的入口npx webpack --config webpack.config.dev.js。格式就是:npx webpack --config 配置文件名

第二种方式:在package.json文件里配置这个命令。比如我想在执行npm run serve的时候是把只配置的东西执行。就是如下的配置方式:

"scripts": {
    "serve": "webpack --config webpack.config.dev.js"
},

以上就是我们需要在写自己配置的时候,对于配置文件名的处理。

六、配置项目中文件的出口入口

const path = require('path');

module.exports = {
 // 设置编译的模式 development/production(默认)
 mode: 'production',
 // 设置编译的入口文件(项目中一般开发的代码都要放置到SRC下)
 entry: './src/main.js',
 // 设置编译的出口文件
 output: {
  // 编译后的文件名  [hash]编译的时候会随机在名字中生成唯一的哈希值,以此保证每一次编译出来的文件是不一样的
  filename: 'bundle.[hash].min.js',
  // 输出的目录(需要是绝对路径,使用path完成)
  path: path.resolve(__dirname, 'build')
 }
};
配置出口入口
配置出口入口

七、webpack常用插件

我们把插件的相关配置都放在plugins中,是一个数组

html-webpack-plugin

  • 想要使用插件,需先安装插件$yarn add html-webpack-plugin -D
  • 插件的作用: 每一次代码更改,重新编译后,都需要手动的去更改指定HTML页面中的导入的JS信息,而这个插件帮我们处理的就是对于HTML的编译和导入文件的自动处理
目录结构
目录结构
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); 

module.exports = {
    mode: 'production',
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    // 在WEBPACK中使用插件
    plugins: [
        // 配置指定的HTML页面模板(后期在编译的时候会把编译好的资源文件自动导入到我们的页面模板中)
        new HtmlWebpackPlugin({
            // 模板的路径
            template: './public/index.html',
            // 编译后生成的文件名
            filename: 'index.html',
            // 是否把编译的资源文件导入到页面中,设置HASH值(清除强缓存,和OUTPUT设置HASH值是一样的)
            hash: true,
            // 把模板中的HTML代码也进行压缩编译(配置规则)
            // https://github.com/kangax/html-minifier
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                removeAttributeQuotes: true,
                removeEmptyAttributes: true
            }
        }),
    ]
}

clean-webpack-plugin

  • 想要使用插件,需先安装插件$yarn add clean-webpack-plugin -D
  • 插件的作用: 每一次打包的时候都把之前打包的内容清空掉,也就是在编译好的文件夹下只保留最新打包的文件
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); 
//这里需要把它解构出来,否则不能使用
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    mode: 'production',
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    // 在WEBPACK中使用插件
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
            hash: true,
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                removeAttributeQuotes: true,
                removeEmptyAttributes: true
            }
        }),
        // 每一次打包都把之前打包的清空
        new CleanWebpackPlugin()
    ]
}

八、webpack-dev-server

可以帮我们创建一个web服务(不在用vscode的live server了),服务可以做以下的事情:

  • 自动监听代码的改变,如果代码改变自动编译
  • 自动帮我们打开浏览器渲染页面
  • 重新编译后自动刷新浏览器看到最新的效果

【除非配置项更改了,需要自己重新执行,否则直接都基于它完成自动化处理】

  • 使用它的时候,需要执行的命令是$npx webpack-dev-server,这样可以一直监听
// 配置DEV-SERVER  编译后的结果放在计算机内存中,并不会向之前的webpack命令一样,把编译后的东西放到build下,dev-server仅仅是在开发模式下,随时编译并且预览的,项目要部署的时候,还是需要基于webpack编译打包的
 devServer: {
  // WEB服务的端口号
  port: '3000',
  // 开启GZIP压缩
  compress: true,
  // 指定资源访问的路径
  contentBase: path.resolve(__dirname, "build"),
  // 自动打开浏览器
  open: true,
  // 开启热更新
  hot: true,
  // Proxy跨域代理
  // proxy: {
  //  '/': 'http://127.0.0.1:8888'
  // }
 },

九、多入口 多出口配置

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {
 CleanWebpackPlugin
} = require('clean-webpack-plugin');

// 配置多页面模板
const htmlPlugins = ['index', 'login'].map(item => {
 return new HtmlWebpackPlugin({
   template: `./public/${item}.html`,
   filename: `${item}.html`,
   // chunks: ['jquery', item], // 指定当前页面的依赖项,先引谁就把谁放在前面
   chunks: [item],
   minify: {
    collapseWhitespace: true,
    removeComments: true,
    removeAttributeQuotes: true,
    removeEmptyAttributes: true
   }
  });
});

module.exports = {
  // 基础配置
  mode: 'production',
  // entry: './src/main.js',
  // 多入口 KEY:VALUE
  entry: {
    index: './src/main.js',
    login: './src/login.js',
    // 如果不想把JQ合并在其它的JS中,想独立打包出来(多个页面公共的部分我们可以独立打包出来)
    // jquery: 'jquery'  //内置模块不需要放在src目录下
  },
   output: {
    // [name]多入口中配置的属性名 index/login
    filename: '[name].[hash].min.js',
    path: path.resolve(__dirname, 'build')
  },
  // 配置DEV-SERVER
  devServer: {
    port: '3000',
    compress: true,
    open: true,
    hot: true
  },
  plugins: [
   // 配置指定的HTML页面模板
   ...htmlPlugins,
   // 每一次打包都把之前打包的清空
   new CleanWebpackPlugin()
  ]
};

十、webpack加载器loader

加载器写在 module 里面

处理CSS样式

  • 安装需要的模块$yarn add css-loader style-loader less less-loader autoprefixer postcss-loader -D
  • 需要配置下面的三个,为了处理css的前缀以及兼容的
  • 这种方式下css是内嵌式的
module.exports = {
   // 配置WEBPACK的加载器LOADER
   module: {
      // 设置规则和处理方案  默认执行顺序:从右到左、从下向上
      rules: [{
        // 匹配哪些文件基于正则处理(此处是处理CSS/LESS文件)
         test: /\.(css|less)$/i,
         use: [
             "style-loader", // 把处理好的CSS插入到页面中(内嵌式)
            "css-loader", // 处理@import/URL这种语法
            "postcss-loader", // 设置CSS前缀(处理兼容 需要搭配autoprefixer一起使用,需要而外再配置一些信息)
           "less-loader" // 把LESS编译为CSS
     ]
    }]
   }
};

postcss.config.js

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

package.json

// https://github.com/browserslist/browserslist 学习网站
/*
 * "> 1%" 兼容99%的浏览器
* "last 2 versions" 兼容最近的两个版本
*/
"browserslist": [
    "> 1%",   
    "last 2 versions"
]

抽离CSS到指定的目录下 mini-css-extract-plugin

它是一个插件,使用插件先下载

  • $ yarn add mini-css-extract-plugin –D

这种执行完css是通过link标签引入到页面的

const MiniCssExtractPlugin=require('mini-css-extract-plugin');
module.exports = {
    plugins: [
        //=>使用插件
        new MiniCssExtractPlugin({
            //=>设置编译后的文件名字
            filename: 'main.[hash].css'
        })
    ],
    module: {
        rules: [{
            test: /\.(css|less)$/,
            use: [
                // "style-loader",
                //=>使用插件中的LOADER代替STYLE方式
                MiniCssExtractPlugin.loader,
                "css-loader",
                "postcss-loader",
                "less-loader"
            ]
        }]
    }
}

优化项压缩CSS/JS

$yarn add optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin terser-webpack-plugin -D

const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsWebpackPlugin= require('optimize-css-assets-webpack-plugin');

module.exports = {
    //=>设置优化项
    optimization: {
        //=>设置压缩方式
        minimizer: [
            //=>压缩CSS(但是必须指定JS的压缩方式)
            new OptimizeCssAssetsWebpackPlugin(),
             //=>压缩JS
            new TerserPlugin()
        ]
    }
};

十一、webpack中图片的处理

$yarn add file-loader url-loader html-withimg-loader -D

module.exports = {
    module: {
        //=>模块规则:使用加载器(默认从右向左执行)
        rules: [{
            // 图片的处理  file-loader就是编译图片的加载器
             test: /\.(png|jpe?g|gif|ico|bmp|svg|eot|ttf|woff|woff2)$/i,
             use: [{
                  // url-loader在编译的时候,会把符合条件的图片进行BASE64,对于不符合条件的还是继续使用file-loader处理
                loader: "url-loader",
                options: {
                   limit: 100 * 1024,
                   // 在编译的时候,把图片都放在统一的IMAGES文件夹下
                   name: 'images/[name].[hash].[ext]',
                   esModule: false
                }
            }],
            include: path.resolve(__dirname, 'src'),
            exclude: /node_modules/
        }, {
          // 编译HTML中的图片的,把其按照上述图片的处理机制处理
           test: /\.html$/,
           use: ['html-withimg-loader']
      }]
    }
}

十二、基于babel实现ES6的转换和ESLint语法检测

$yarn add babel-loader @babel/core @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-transform-runtime -D

$yarn add @babel/runtime @babel/polyfill

$yarn add eslint eslint-loader -D

@babel/polyfill和其它的webpack加载器和插件不一样,其它的是编译时(编译代码的时候处理),而polyfill是运行时,是在代码运行的时候,把一些ES7等特殊的语法进行兼容处理

  1. 需要安装在生产环境下,因为上线代码运行时也是需要的 @babel/runtime @babel/polyfill
  2. 需要一个插件的支持 @babel/plugin-transform-runtime
module.exports = {
    module: {
        rules: [{
            test: /\.js$/,
            use: [{
                loader: 'babel-loader',
                options: {
                    //=>转换的语法预设(ES6->ES5)
                    presets: [
                        "@babel/preset-env"
                    ],
                    //=>基于插件处理ES6/ES7中CLASS的特殊语法
                    plugins: [
                        ["@babel/plugin-proposal-decorators", {
                            "legacy": true
                        }],
                        ["@babel/plugin-proposal-class-properties", {
                            "loose": true
                        }],
                        "@babel/plugin-transform-runtime"
                    ]
                }
            }], //=>, "eslint-loader"
            //=>设置编译时忽略的文件和指定编译目录
            include: path.resolve(__dirname, 'src'),
            exclude: /node_modules/
        }]
    }
}

十三、分享

对于我来说的一个好消息吧,前天自己鼓足了勇气加了自己膜拜的前端大神,虽然,尬聊了两句(粉丝见到偶像是不是都是这样🙈),但是也给了自己在前端路上的信心。激动的心情已经无法有言语诉说😊

最后我们大家一起加油,期待有一天我们都可以与膜拜的大神作为同事😊

加油
加油