webpack基础

2 minute read

前言

webpack是一款非常流行的前端用于构建自动化开发的工具,与之前的gulp和grunt功能非常相似,它们都是一款通过JavaScript来构建Web网站的自动化工具!

本质上,webpack是一个现代JavaScript应用程序的静态模块打包器(module bundler)。当webpack处理应用时,它会递归构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。

我们可以通过下面的一张图来理解这一部分功能

webpack可以让一切都变得非常简单,它提倡模块化进行开发,并提供通过工具的操作简化和提高前端开发工作

入门

webpack是基于NodeJs平台运行的,所以,在使用webpack之前,请先保证自己的开发环境下有NodeJs的运行环境

  1. 安装webpack

    目前最新的webpack版本是:4.42.1。要安装最新版本或者特定版本,运行以下命令:

    $ npm install --save-dev webpack
    $ npm install --save-dev webpack@<version>
    

    如果使用webpack 4+ 版本,你还需要安装CLI。

    $ npm install --seve-dev webpack-cli
    
  2. 编写JS代码,通过webpack来进行打包处理

    我在上面的代码中,创建了一个app.js和person.js,然后在app.js中通过ES6的模块化开发,引入这个文件。

    正常情况下,我们是不可能在浏览器中使用export与import等这些关键字的,现在,我们可以在这里通过webpack来处理一下

    $ webpack app.js -o bundle.js
    

    说明:上面的命令是将app.js通过webpack打包以后,编译成bundle.js,打包完成以后,我们就可以在浏览器中正常使用这个JS文件了,下面编译过后的代码:

    运行结果如图:

    通过上面的入门,我们可以得出以下结论:

    • webpack可以将ES6的代码转换成我们浏览器能使用的代码(正常情况下,ES6的代码需要在NodeJS环境下面才能够运行)
    • 我们可以通过webpack进行模块化开发,同时,在模块化开发的过程中,把这些模块化的代码运行在浏览器上面

    在这里,我们的NodeJs的一切比较良好的模块化开发特性,以及ES6甚至更高的一些高效语法就可以在这里进行使用了。

webpack.config.js的配置

在上面的入门代码里面,我们知道,如果要把我们的代码进行打包处理,则需要手动的去执行一条命令,但是这样操作非常麻烦,我们不可能每次更改代码后都去手动的执行一次命令,这个时候,我们可以通过一个配置文件,来配置我们的webpak操作。

webpack的配置文件,我们一般命名为webpack.config.js这个文件,在这个文件里,有四大部分,如下:

  • 入口(entry)
  • 出口(output)
  • loader
  • 插件(plugins)

入口 entry

首先,我们现在项目的根目录下面,创建一个webpack.config.js文件,然后打开文件,编写如下代码

var config = {
    entry: 'app.js'
};
module.exports = config;

说明:上面的./src/app.js代表的就是这个程序的入口文件

出口 output

output属性告诉webpack在哪里输出它所创建的bundles,以及如何命名这些文件。你可以通过在配置中指定一个output字段,来配置这些处理过程:

const path = require('path');

var config = {
    entry: 'app.js',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js'
    }
};
module.exports = config;

在上面的代码中,我们通过output.filenameoutput.path属性,来告诉webpack bundle的名称,以及我们想要打包到哪里。

就像之前的入门里面,我们使用webpack app.js -o bundle.js一样,前面是入口文件,后面是出口文件

loader

loader让webpack能够去处理那些非JavaScript文件(webpack自身只能理解JavaScript)。loader可以将所有类型的文件转换为webpack能够处理的有效模块,然后你就可以利用webpack的打包能力,对它们进行处理。

例如,我可以让webpack来处理scss的预处理CSS文件,也可以让webpack来处理vue的文件,这都是webpack所具备的功能,但是,webpack如果想要使用这些功能,必须要使用第三方的模块,而这些第三方的模块,我们就叫做loader。

注意,loader能够import任何类型的模块(例如.css文件),这是webpack特有的功能,其它打包程序或任务执行器可能并不支持。我门认为这种语言扩展是很有必要的,因为这可以使开发人员创建出更准确的依赖关系图。

在配置loader的时候,里面有两个必填属性,一个是test,一个是use属性:

  1. test属性,用于标识出应该被对应的loader进行转换的某个或某些文件。
  2. use属性,表示进行转换时,应该使用哪个loader。
const path = require('path');

var config = {
    entry: 'app.js',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.scss/,
                use: 'sass-loader'
            }
        ]
    }
};
module.exports = config;

以上的配置中,对一个单独的module对象定义了rules属性,里面包含两个必须属性:testuse。这告诉webpack编译器如下信息:

”嘿,webpack编译器,当你碰到在require()/import语句中被解析为.scss的路径时,在你对它们打包之前,先试用sass-loader处理一下。“

在此处说明一下,webpack的loader必须配置在module.rules这个属性里面!

插件 Plugins

插件在此先不做说明,后期案例具体说明

webpack与Babel的结合

我们刚刚说过一件事情,就是我们在使用webpack的时候,可以将我们的ES6代码的模块化运用的非常好,但是,有些时候,浏览器并不能识别所有的ES6代码,这个时候,我们就需要使用一个第三方模块来处理我们的ES6代码,而这个模块就是Babel

Babel是一款代码翻译工具,无论是现在的ES6,还是Vue,这些大名鼎鼎的框架都是基于Babel的,如果脱离的Babel,这些框架运行起来会非常困难。

首先,我们要知道Babel的第三方模块如何去使用,在Babel的中文官网当中,我们可以找到Babel在webpack中的配置,具体使用步骤如下:

  1. 安装:

    $ npm install --save-dev babel-loader @babel/core
    
  2. 通过webpack.config.js进行配置

    module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader'
                }
            ]
        }
    

    说明:上面的代码的意思是告诉webpack,如果碰到.js文件,就是用第三方的模块babel-loader来进行处理一下,但是并不是所有的js文件都要处理,在node_modules这里面的文件就不要处理了。

  3. 配置代码的env环境

    现在的ECMAScript标准已经发展到ES2020了,所以,我们在书写JS代码的时候,可以使用一些比较新的特性,例如我们的异步处理当中的asyncawait这些方法。如果要让我们的浏览器支持asyncawait这些方法,仅仅是通过webpack和babel的一些常规处理方法是不行的,我们需要使用@babel/preset-env@babel/polyfill这两个插件。

    • 安装@babel/preset-env@babel/polyfill

      $ npm install --save-dev @babel/preset-env
      $ npm install --save @babel/polyfill
      
    • 修改webpack.config.js中的entry入口

      entry: [
              "@babel/polyfill",
              path.join(__dirname, "./app.js")
      ]
      
    • 修改babel配文件

      创建.babelrc文件,修改内容如下:

      {
          "presets": [
            [
              "@babel/preset-env",
              {
                "useBuiltIns": "entry"
              }
            ]
          ]
      }
      

    通过上面的配置以后,我们就可以正常的使用ES的新特性了

webpack生成编译文件

当我们配置好webpack的配置文件以后,所有的操作我们就都可以交给webpack来处理了,这个时候,我们在进行编译的过程当中,要让webpack来使用我们的配置文件,这个时候,需要执行如下命令:

$ webpack --config webpack.config.js

命令执行成功后,就会在指定的目录生成我们的目录文件,这个时候,我们可以把这条命令写在package.json这个文件里面

在package.json这个文件里面,我们在scpirts这个属性里面,添加如下代码:

{
  ...
  "scripts": {
    ...
    "build": "webpack --config webpack.config.js"
    ...
  }
  ...
}

上面的build就是我们添加的自定义命令,添加完成以后,我们就可以在控制台输入npm run build来执行上面的命令,最终会执行我们的webpack的打包操作。

webpack开发环境与生产环境

我们在使用webpack进行打包配置的时候,我们不可能一直去执行打包的操作,然后去把生成的bundle.js这个文件引入到我们的HTML文件当中,这个对开发非常麻烦,也非常繁琐,这个时候,webpack本身也考虑到这个问题。

官方针对这种情况推出了一个小型的基于express的迷你http服务器,可以让我们开发的网页运行在http的服务器上面,然后做到实时编写,实时编译, 这个时候,效果就非常高!这一种搞笑的环境,我们叫做webpack开发环境。

webpack的开发环境当中的http服务器依赖于webpack-dev-server,从名字当中我们就可以看出这是webpack的开发服务器,接下来的过程里面,我们来了解一下如何配置webpack的开发环境。

  1. 首先,我们需要安装webpack-dev-server

    $ npm install webpack-dev-server --save-dev
    
  2. 在package.json的文件当中,添加开发环境的启动命令

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

    通过上面的dev代码,我们发现,这个时候的打包命令有webpack变成了webpack-dev-server,这个时候,我们会在这个地方以webpack-dev-server的方式来打包,但是,我们需要弄清楚一点,这个时候,我们使用的配置文件都是webpack.config.js这个文件,而我们的build命令与dev命令又是不一样的,这个时候,我们就需要在webpack.config.js的文件当中做一些区分,具体代码如下所示:

    const path = require('path');
       
    // 判断是否为开发环境
    var isDev = process.env.NODE_ENV === "development" ? true : false;
       
    var config = {
        entry: [
            "@babel/polyfill",
            path.join(__dirname, "./app.js")
        ],
        output: {
            path: path.join(__dirname, 'dist'),
            filename: 'bundle.js'
        },
        module: {
            rules: [
                {
                    test: /\.scss/,
                    use: 'sass-loader'
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader'
                }
            ]
        }
    };
       
    if (isDev) {
        config.devServer = {
            host: '0.0.0.0',
            port: 4200,
            overlay: {
                error: true
            }
        }
    }
       
    module.exports = config;
    

    说明:上面的代码中,我们看到我们使用了一个叫process.env.NODE_ENV的来判断当前环境是否开发环境,这个时候,我们如何去区分他们的环境呢?

    我们刚刚在package.json的文件当中,通过build与dev的两个命令来区分,但是,这只是启动方式改变了,后面的配置文件还是同一个,为了区分这个环境,我们需要一个第三方的模块cross-env,这个模块可以在启动命令前面添加启动参数,这个启动参数会传递到NodeJs的运行环境当中。

    现在,我们安装cross-env以区分不同的启动方式

    $ npm install cross-env --save-dev
    

    安装完成以后,我们把上面的scripts中的启动命令改成如下:

    "scripts": {
      "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
      "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
    }
    

    在上面的代码里面, 我们分别在启动命令前面用cross-env来区分NODE_ENV到底是production还是development

    区分了环境以后,我们用了一个变量isDev来判断,如果是开发环境,我们在后面配置了一个devServer那么,我们可以在里面配置它的服务器地址,服务器端口,服务器。

写在最后

贴一下webpackdemo配置,如有错误,望指正。

  1. package.json

    {
      "name": "webpackdemo",
      "version": "1.0.0",
      "description": "",
      "main": "app.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
        "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "@babel/core": "^7.9.0",
        "@babel/preset-env": "^7.9.5",
        "babel-loader": "^8.1.0",
        "cross-env": "^7.0.2",
        "sass-loader": "^8.0.2",
        "webpack": "^4.42.1",
        "webpack-cli": "^3.3.11",
        "webpack-dev-server": "^3.10.3"
      },
      "dependencies": {
        "@babel/polyfill": "^7.8.7"
      }
    }
    
  2. webpack.config.js

    const path = require('path');
       
    // 判断是否为开发环境
    var isDev = process.env.NODE_ENV === "development";
       
    var config = {
        entry: [
            "@babel/polyfill",
            path.join(__dirname, "./app.js")
        ],
        output: {
            path: path.join(__dirname, 'dist'),
            filename: 'bundle.js'
        },
        module: {
            rules: [
                {
                    test: /\.scss/,
                    use: 'sass-loader'
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader'
                }
            ]
        }
    };
       
    if (isDev) {
        config.devServer = {
            // host: '0.0.0.0',
            port: 4200,
            overlay: {
                error: true
            }
        }
    }
       
    module.exports = config;
    
  3. babelrc

    {
        "presets": [
          [
            "@babel/preset-env",
            {
              "useBuiltIns": "entry"
            }
          ]
        ]
    }