在搞定了CSS工作流之后,前端三剑客还有一个重要的JavaScript也需要工作流。

  1. 安装Webpack
  2. 为项目配置Webpack
  3. export与import
  4. Webpack自动监听文件改变
  5. 异常处理

安装Webpack

很久以来,JS都是像脚本一样随写随用,开发比较随意,因为没有很工程化的东西。

现在有了Webpack,可以方便的打包很多东西,也包括之前说的CSS文件。但最重要的,还是Webpack作为JS工程化的重要工具。

继续沿用这个项目,在assets目录下创建一个scripts目录用来存放开发中的JS文件,创建一个app/assets/scripts/App.js文件如下:

function Person(fullName, favColor) {
    this.name = fullName;
    this.favoriteColor = favColor;
    this.greet = function () {
        console.log(this.name + " likes " + this.favoriteColor);
    }
}

var john = new Person("John Doe", "blue");
john.greet();

很显然,这个文件定义了一个类,然后使用了类。不过一般定义和使用不放在同一个文件里,为了将类单独抽离,就像CSS一样,就需要工程化JS的编写方式。

scripts目录下创建modules目录,然后新建一个Person.js文件,把类的文件复制进来,然后在App.js中导入:

var Person = require("./modules/Person");

var john = new Person("John Doe", "blue");
john.greet();

看起来很像之前写Gulp任务文件,但实际上,这么做是无法运行这个文件的,使用Node.js也不行。这就需要前端工程化组件Webpack出场了。

和之前的工具类似,我们安装Webpack,让其监视App.js,然后到里边把依赖的内容拼合成一个文件,重整之后生成最终的JS文件。

先来安装,这里需要全局安装,因为Webpack现在的地位相当于像IDE一样的工具,所有项目都会用到。

npm install webpack -g

npm自动安装的版本是4.31版本。

为项目配置Webpack

为了在项目中使用Webpack,需要在项目根目录下创建一个文件,这个文件的名称必须是webpack.config.js,告诉Webpack当前目录是一个Webpack项目。

先来在其中写一个JS对象:

var path = require("path");

module.exports = {
    entry: "./app/assets/scripts/App.js",
    output: {
        path: path.resolve(__dirname, "./app/temp/scripts"),
        filename: "App.js"
    }
};

Webpack需要定义一个起点,类似于一个JS的根文件,或者说像很多编译型程序的main函数,从这个起点出发不断的把所有的依赖解析进来。

之后配置一个输出的路径和文件名,这样就会把各种依赖都打包成为一个文件。Webpack的输出路径必须是绝对路径,这里使用了Node.js的内置库,解析当前目录然后拼合成绝对路径。

至于module.exports之后再来学习。

然后在项目根目录下直接运行webpack,webpack会自动检测这个配置文件然后打包JS代码。

注意,Webpack 4.31在使用命令行之前,需要运行npm i webpack webpack-cli --save-dev在当前目录下安装命令行工具才行。

运行之后有如下输出:

Hash: 6951834551b2fbe8c111
Version: webpack 4.31.0
Time: 369ms
Built at: 2019-05-20 14:16:45
 Asset       Size  Chunks             Chunk Names
App.js  984 bytes       0  [emitted]  main
Entrypoint main = App.js
[0] ./app/assets/scripts/App.js 102 bytes {0} [built]
[1] ./app/assets/scripts/modules/Person.js 208 bytes {0} [built]

还有一些Warning暂时不用看,因为还没有具体深入配置。通过输出可以看到,构建的文件里包括Person.js。此时看看目标目录,确实生成了一个D:\Coding\frontendworkflow\app\temp\scripts\App.js,打开看看代码确实看不懂,这是因为经过重新整理。

看不懂没事,运行吧,咦,报错。。。。第一次使用Webpack就坑了么?

export与import

回到这条语句:var Person = require("./modules/Person");,实际上将Person作为一个包导入,然而要从这个包导入什么内容,没有明确的定义。

Webpack通过export和import来导入内容。导入导出包,由于JS是面向对象的语言,实际导入的都是一个对象。

Node.js执行的时候,如果导入包,会默认到包里寻找一个叫做exports的对象,require这个函数就会返回exports这个对象,通过这个对象来使用包中的代码。

可以先在Person.js里写点代码测试一下:

function Person(fullName, favColor) {
    this.name = fullName;
    this.favoriteColor = favColor;
    this.greet = function () {
        console.log(this.name + " likes " + this.favoriteColor);
    }
}

exports.exampleProperty = "Super";
exports.exampleFunction = function () {
    console.log("THis is demo");
};

然后在App.js里注释掉生成Person对象的代码,加一行:

var Person = require("./modules/Person");

// var john = new Person("John Doe", "blue");
// john.greet();

console.log(Person);

此时再通过Webpack打包,然后加载页面,可以看到浏览器控制台里:

{exampleProperty: "Sunper", exampleFunction: ƒ}
exampleFunction: ƒ ()
exampleProperty: "Sunper"
__proto__: Object

说明为exports对象设置的属性都被导入了,此时可以在App.js里添加两行试试:

var Person = require("./modules/Person");

// var john = new Person("John Doe", "blue");
// john.greet();

console.log(Person);
Person.exampleFunction();
console.log(Person.exampleProperty);

再打包后发现正常执行了。

所以,我们在抽出一个JS文件的时候,一定要注意编写exports对象。以让Webpack可以正常打包。

依据这个思路,我们的Person.js文件只有一个Person类需要让其他文件使用:

function Person(fullName, favColor) {
    this.name = fullName;
    this.favoriteColor = favColor;
    this.greet = function () {
        console.log(this.name + " likes " + this.favoriteColor);
    }
}

module.exports = Person;

这样导出后,导出的对象就是这个构造函数Person。

然后把App.js还原成原来的状态,再打包,就一切正常了。

凡是通过NPM安装的包,都为Webpack导入而编写好了对应的代码。比如还可以导入已经安装的jQuery

var Person = require("./modules/Person");
var $ = require('jquery');

var john = new Person("John Doe", "blue");
john.greet();

$("h1").remove();

打包并且刷新页面后,所有的H1元素都不见了。

自动监听文件改变

现在我们每次修改JS文件,都去执行Webpack,还不够自动化,应该像Gulp那样,自动检测所有的JS文件变化并自动打包。之前安装了全局的Webpack,这里还必须安装当前项目的Webpack:

npm install webpack --save-def

我们给Webpack编写一个Gulp任务文件:gulp/tasks/scripts.js

var gulp = require('gulp'),
    webpack = require('webpack');

gulp.task('scripts', function (done) {
    webpack(require('../../webpack.config'),function () {
        console.log("Mission Completed!");
    });
    done();
});

webpack自己就是一个函数,第一个参数是当前JS文件路径去找到配置文件的相对路径,第二个参数是执行成功的回调函数。

之后我们需要在watch里添加监视开发中的所有js文件的功能,然后调用这个任务来打包JS文件:

var gulp = require('gulp'),
watch = require('gulp-watch'),
browserSync = require('browser-sync').create();

gulp.task('watch', function() {
  ......

  watch('./app/assets/scripts/**/*.js',function () {
  gulp.start('scriptRefresh');
  })
});

gulp.task('scriptRefresh',['scripts'], function () {
  browserSync.reload();
});

这里使用任务依赖,更新页面的任务会在重新打包完成后执行。每次监视JS文件有变化的时候,就自动让Webpack进行打包,然后重载页面。实验了发现运行良好,有任何修改都很方便的自动刷新。

异常处理

由于Webpack没有走styles任务的异常处理,所以必须自行编写一个。

异常处理就在scripts任务的回调函数中,修改如下:

var gulp = require('gulp'),
    webpack = require('webpack');

gulp.task('scripts', function (done) {
    webpack(require('../../webpack.config'), function (err, stats) {
        if (err) {
            console.log(err.toString());
        }
        console.log(stats.toString());
        console.log("Mission completed!");
    });
    done();
});

第一个参数就是错误对象,第二个是执行状态,都可以打印出来看一下。

有了异常处理之后有错误也不会停止watch任务了。