[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)

一、模块化

1.什么是模块化

模块化就是把系统分离成独立功能的方法,这样我们需要什么功能,就加载什么功能

  • 每个模块都是独立的,良好设计的模块会尽量与外部的代码撇清关系,以便于独立对其进行改进和维护
  • 可以重复利用,而不用经常复制自己之前写过的代码
python语言就是模块化的,每个py文件就是一个模块,互相之间可以导入使用。
 

模块化主要解决两个问题,"命名冲突"、"文件依赖"。

1)命名冲突

在一个js文件中,如果我们的两个变量名字相同,则前面的会被覆盖,而在不同的js模块,就可以解决命名冲突问题。

2)文件依赖

例如在我们使用vue-router.js插件时,他依赖vue.js,所以我们必须先导入vue.js,然后才能导入vue-router.js:

// b.js依赖a.js,标签的书写顺序必须是:
<script src='a.js' type='text/javascript'>script>
<script src='b.js' type='text/javascript'>script>

这种按依赖顺序导入的方式是同步导入。如果某个js文件很大,或者导入的时候出错,则可能整个页面被阻塞。

2.Commonjs(nodejs服务端)

CommonJs是服务器端模块的规范,Node.js采用了这个规范。

根据CommonJS规范,一个单独的文件就是一个模块

模块导出使用module.exports:

// math.js
var
math = { add:function(a,b){ return a+b; } } module.exports = math

加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象:

var math = require('./math.js');
math.add(2, 3);  // 输出5

第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。您会注意到 require 是同步的。

CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了AMD(Async module define)解决方案。

3.AMD

AMD 即 Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范。

AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:

require([module], callback);

第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

require(['math'], function (math) {
  math.add(2, 3);
});

math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。

4.ES6 Module(浏览器客户端)

Vue使用ES6 Module。

 
参考: http://es6.ruanyifeng.com/
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
但是,目前浏览器不直接支持ES6 Module的import操作。我们需要借助webpack来实现浏览器支持模块化js。

二、webpack介绍和使用

由于webpack依赖node.js服务器环境,我们需要先安装node.js,再安装webpack。

1.安装node.js和webpack

1)下载安装node.js

下载地址:http://nodejs.cn/download/

安装:windows下安装很简单

安装完毕后,cmd中执行:

C:\Users\Administrator>node -v
v12.15.0

node.js安装完,默认npm包管理器也被安装好了。

2)安装webpack

执行命令:

npm i webpack@3.12.0 -g

3)使用国内淘宝npm镜像

如果npm安装软件很慢,则安装一个cnpm工具,这是淘宝提供的和npm类似的命名,的源在国内。

npm install -g cnpm --registry=https://registry.npm.taobao.org

安装完毕后,就可以使用cnpm命名了,使用方法和npm基本一致。

2.ES6 Module的问题

例如,有如下场景。

我们有两个js文件,main.js依赖module.js:

// module.js

var person = {
    name:'Leokale',
    fav:function(){
        alert('我是person对象下的fav函数');
    }
}

export default person;
// main.js

import person from './module'

console.log(person.name);
console.log(person.fav());

此时,我们如何在index.html中使用ES6 Module的语法import导入main.js:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEXtitle>
head>
<body>
    <script src="./main.js">script>
body>
html>

使用浏览器F12查看控制台,发现报错:

3.使用webpack打包js模块

鉴于2.中我们无法直接使用js模块。我们使用webpack先对js文件进行打包:

进入windows的cmd或者pycharm的terminal,进入目录(main.js入口js文件所在目录),执行打包命令:

(venv) D:\pycharm_workspace\vue_learn\es6module>webpack -v
3.12.0

(venv) D:\pycharm_workspace\vue_learn\es6module>webpack ./main.js ./bundle.js
Hash: 7407b3b8c075c448fd75
Version: webpack 3.12.0
Time: 44ms
    Asset     Size  Chunks             Chunk Names
bundle.js  3.13 kB       0  [emitted]  main
   [0] ./main.js 86 bytes {0} [built]
   [1] ./module.js 145 bytes {0} [built]

main.js是入口js模块,bundle.js是我们指定的输出js文件名。

执行完毕后,我们可以看到目录中生成了bundle.js文件:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第1张图片

这个bundle.js文件就是webpack帮我们将main.js和module.js捆绑打包好的js文件。

此时,我们就可以在index.html中去引用了:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEXtitle>
head>
<body>
    <script src="./bundle.js">script>
body>
html>

此时,页面弹出alert框,说明main.js中的console.log(fav())运行成功:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第2张图片

三、ES6 module的使用

1.js模块抛出多个对象

// module.js

var person = {
    name:'Leokale',
    fav:function(){
        alert('我是person对象下的fav函数');
    }
}
var name = 'myname';
var age = 32;

// 抛出多个对象或变量
export {name,age};  // 抛出name和age
export var address = 'NO1.street';
export default person;

在module.js中,同时抛出了name、age、address变量以及person对象。

// main.js

import * as objs from './module'

console.log(objs);

在main.js中导入module.js中抛出的所有对象和变量。注意,这里使用"*"表示全部,但是必须使用as取一个别名。

打印结果:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第3张图片

2.模块化引入vue.js

vue.js默认就是支持ES6Module模块化的。所以我们可以在我们的js文件中引入vue.js。

为了方便,现将vue.js拷贝到和入口js文件同目录:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第4张图片

然后在main.js中引入:

// main.js
import Vue from './vue.js'

new Vue({
    el: "#app",
    data() {
        return {
            msg: '我是vue实例'
        }
    },
    template: `
    
    `
})

3.实现模块解耦

在main.js中,我们引入了vue.js,并且创建了Vue的实例。

如果我们想要在vue中使用组件,我们不要在main.js中实现组件,而是利用模块解耦的方式,在其他的js文件中实现App组件(例如App.js):

// App.js

let App = {
    data(){
        return {}
    },
    template:`
        
我是App组件
` } export default App;

然后,再在main.js中引入App.js:

// main.js

import Vue from './vue.js'
import App from './App.js'

new Vue({
    el: "#app",
    data() {
        return {
            msg: '我是vue实例'
        }
    },
    // 挂载App组件
    components:{
        App
    },
    // 在这里是用App组件
    template: `
        
` })

然后使用webpack打包:

webpack ./main.js ./bundle.js

打包完毕后,在index.html中使用bundle.js:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEXtitle>
head>
<body>
    <div id="app">div>
    <script src="./bundle.js">script>
body>
html>

特别注意 <div id='app'>div> 一定不要忘了,因为最终渲染的地方是在这里。

运行结果:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第5张图片

四、实现webpack自动编译打包

在前面的章节中,我们每次修改了js模块代码,都需要手工使用webpack工具(是安装在我们机器上的一个软件)来进行打包。下面,我们使用webpack模块来实现自动打包。

1.生成package.json配置文件

在我们的项目中,先使用npm命令生成项目的配置文件package.json(类似于django中的settings.py)。

在命令行中项目目录下执行:

(venv) D:\pycharm_workspace\vue_learn\es6module>npm init --yes
Wrote to D:\pycharm_workspace\vue_learn\es6module\package.json:

{
  "name": "es6module",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

注意: --yes 选项的意思是默认交互确认都选择yes。如果不使用 --yes ,则自定义信息。

执行完毕后,可以看到项目目录中产生了package.json文件:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第6张图片

package.json中的内容,就是命令执行后打印出来的json数据(绿色背景部分)。

2.安装webpack开发环境依赖

安装webpack的开发环境依赖。

(venv) D:\pycharm_workspace\vue_learn\es6module>cnpm i webpack@3.12.0 -D
- [0/1] Installing path-parse@^1.0.6platform unsupported webpack@3.12.0 › watchpack@1.6.0 › chokidar@2.1.8 › fsevents@^1.2.7 Package require os(darwin) not compatible with your platform
(win32)
[fsevents@^1.2.7] optional install error: Package require os(darwin) not compatible with your platform(win32)
√ Installed 1 packages
√ Linked 272 latest versions
[1/1] scripts.postinstall webpack@3.12.0 › uglifyjs-webpack-plugin@^0.4.6 run "node lib/post_install.js", root: "D:\\pycharm_workspace\\vue_learn\\es6module\\node_modules\\_uglifyjs-web
pack-plugin@0.4.6@uglifyjs-webpack-plugin"
[1/1] scripts.postinstall webpack@3.12.0 › uglifyjs-webpack-plugin@^0.4.6 finished in 61ms
√ Run 1 scripts
Recently updated (since 2020-02-02): 1 packages (detail see file D:\pycharm_workspace\vue_learn\es6module\node_modules\.recently_updates.txt)
√ All packages installed (308 packages installed from npm registry, used 9s(network 9s), speed 552.74kB/s, json 273(526.93kB), tarball 4.49MB)

当然,使用npm也可以(如果不怕慢的话)。

安装完毕后,我们可以看到package.json发生了变化:

{
  "name": "es6module",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^3.12.0"
  }
}

内容多出了"devDependencies",其中指明了我们刚刚安装的webpack开发环境依赖,版本是3.12.0。

3.修改执行脚本-1

package.json文件是通过 npm init 命令产生的。npm还有另外一个命令:

npm run

执行这个命令的时候,npm会自动去package.json中找"scripts"字段:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
},

此时,这个字段中是一个测试脚本,没什么用。我们对其进行修改:

"scripts": {
  "dev": "webpack ./main.js ./bundle.js"
},

然后使用npm run来执行:

(venv) D:\pycharm_workspace\vue_learn\es6module>npm run dev

> es6module@1.0.0 dev D:\pycharm_workspace\vue_learn\es6module
> webpack ./main.js ./bundle.js

Hash: c7aa55741952b4a92b17
Version: webpack 3.12.0
Time: 375ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  373 kB       0  [emitted]  [big]  main
   [0] (webpack)/buildin/global.js 509 bytes {0} [built]
   [1] ./main.js 367 bytes {0} [built]
   [2] ./vue.js 354 kB {0} [built]
   [6] ./App.js 136 bytes {0} [built]
    + 3 hidden modules

可以看到,使用 npm run dev 相当于执行了 webpack ./main.js ./bundle.js 命令,生成了bundle.js出口文件。

4.修改执行脚本-2

我们再次修改scripts字段的脚本:

"scripts": {
  "dev": "webpack"
},

我们删除了入口js文件和出口js文件。如果此时直接使用 npm run dev ,一定会报错。

那是因为,我们缺少一个webpack的配置文件,webpack.config.js文件(注意,一定要是这个名字):

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第7张图片

那么,这个配置文件要怎么写,可以参考官方文档:https://www.webpackjs.com/concepts/

// webpack.config.js

module.exports = {
    entry: './main.js',
    output: {
        filename:'./bundle.js'
    }
};

或者:

// webpack.config.js

module.exports = {
    entry: {
        main:'./main.js'
    },
    output: {
        filename: './bundle.js'
    }
};

此时,再执行 npm run dev :

(venv) D:\pycharm_workspace\vue_learn\es6module>npm run dev

> es6module@1.0.0 dev D:\pycharm_workspace\vue_learn\es6module
> webpack

Hash: c2b0ee0bf652740d4483
Version: webpack 3.12.0
Time: 327ms
      Asset    Size  Chunks                    Chunk Names
./bundle.js  373 kB       0  [emitted]  [big]  main
   [0] (webpack)/buildin/global.js 509 bytes {0} [built]
   [1] ./main.js 367 bytes {0} [built]
   [2] ./vue.js 354 kB {0} [built]
   [6] ./App.js 136 bytes {0} [built]
    + 3 hidden modules

可以成功打包。

5.实现监听代码变化自动打包

在webpack.config.js配置文件中,添加一行代码:

module.exports = {
    entry: './main.js',
    output: {
        filename:'./bundle.js'
    },
    watch:true
};

然后执行一次 npm run dev ,让其读一次配置文件:

npm run dev

可以看到,终端一直阻塞着:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第8张图片

至此以后,我们修改js代码时,例如在main.js的vue实例中,template多使用一个App组件:

// main.js

import Vue from './vue.js'
import App from './App.js'

new Vue({
    el: "#app",
    data() {
        return {
            msg: '我是vue实例'
        }
    },
    // 挂载App组件
    components:{
        App
    },
    // 在这里是用App组件
    template: `
        
新加一个App组件:
` })

保存后,看实现效果:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第9张图片

可以看到,bundle.js已经自动打包重新生成了。

6.开发环境和生产环境分别使用不同配置文件

在开发环境中,我们希望webpack帮我们监听文件的改变,然后自动打包。

而在生成环境中,我们希望手工运行命令打包全部。

我们可以自定义两个配置文件,来分别对应开发环境和生产环境:

[前端] VUE基础 (7) (模块化、ES6Module、webpack自定义脚手架)_第10张图片

然后,我们在package.json中的scripts字段中配置:

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

其中"dev"对应开发环境,使用 npm run dev 来开启webpack监听,并自动打包。

// webpack.dev.config.js

module.exports = {
    entry: './main.js',
    output: {
        filename:'./bundle.js'
    },
    watch:true
};

"build"对应生产环境,使用 npm run build 来打包。

// webpack.prod.config.js

module.exports = {
    entry: './main.js',
    output: {
        filename:'./bundle.js'
    }
};

注意:这里的"dev"和"build"都是我们自定义的名字,我们可以随意更改。

五、webpack打包css文件

在前面的章节中,我们利用webpack实现了js的打包,以及开发环境下的自动打包。

那么,我们想利用webpack帮我们打包除js文件以外的静态资源,例如css样式文件,该如何使用呢?

1.引子

创建一个css文件,index.css:

/* index.css */
body{
    background-color: antiquewhite;
}

我们在main.js入口js文件中直接导入css文件:

import './index.css'

我们发现,webpack报错:

ERROR in ./index.css
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type.
| body{
|     background-color: antiquewhite;
| }

错误信息中提示我们需要一个loader去处理css文件类型。

在webpack的官方文档中https://www.webpackjs.com/concepts/,提到了4个核心概念:

先理解四个核心概念:

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

其中就有所需要的loader。

2.webpack的loader

首先安装css-loader和style-loader的开发环境依赖:

(venv) D:\pycharm_workspace\vue_learn\es6module>cnpm i css-loader style-loader -D
√ Installed 2 packages
√ Linked 34 latest versions
√ Run 0 scripts
peerDependencies WARNING style-loader@* requires a peer of webpack@^4.0.0 || ^5.0.0 but none was installed
peerDependencies WARNING css-loader@* requires a peer of webpack@^4.0.0 || ^5.0.0 but none was installed
√ All packages installed (37 packages installed from npm registry, used 2s(network 2s), speed 230.62kB/s, json 37(170.77kB), tarball 353.43kB)

安装完毕后,可以看到package.json中的"devDependencies"变为:

"devDependencies": {
  "css-loader": "^3.4.2",
  "style-loader": "^1.1.3",
  "webpack": "^3.12.0"
}

3.配置使用loader

在webpack的开发环境配置文件webpack.dev.config.js中进行配置:

module.exports = {
    entry: './main.js',
    output: {
        filename:'./bundle.js'
    },
    watch:true,
    module:{
        loaders:[
            {
                // 正则表达式,找所有.css结尾的文件
                test:/\.css$/,
                // 'style-loader!css-loader'的意思是,先使用css-loader解析css文件
                // 然后再使用style-loader将其生成

vue文件中的