Django RESTful 系列教程(四)

前后端分離的好處就是可以使前端和后端的開發(fā)分離開來,如果使用 Django 的模板系統(tǒng)县踢,我們需要在前端和后端的開發(fā)中不停的切換,前后端分離可以把前端項(xiàng)目和后端項(xiàng)目分離開來,各自建立項(xiàng)目單獨(dú)開發(fā)蛙粘。那么問題來了垫卤,前端怎么建項(xiàng)目?這就是本章需要解決的問題出牧。

對于任何的工具穴肘,我的哲學(xué)是“工具為人服務(wù),而不是人為工具服務(wù)”舔痕,希望大家不要為了學(xué)習(xí)某個(gè)工具而學(xué)習(xí)评抚,任何工具的出現(xiàn)都是為了滿足不同的需求。這是在學(xué)習(xí)前端工具鏈時(shí)需要牢記的一點(diǎn)伯复,不然等學(xué)完了慨代,學(xué)的東西就全部忘了。前端的世界浩瀚無比啸如,小小的一章并不能很詳盡的介紹它們侍匙,僅僅是作為一個(gè)入門的介紹。

JavaScript 的解釋器 —— node 與 “模塊化”

js 和 python 同為腳本語言叮雳,他們都有自己的解釋器丈积。js 的解釋器是 node 。

在 node 出現(xiàn)前 js 有沒有自己的解釋器呢债鸡?有的,那就是我們的瀏覽器中的 js 引擎铛纬,但是這個(gè)引擎的實(shí)現(xiàn)僅僅是針對瀏覽器環(huán)境的厌均,這個(gè)引擎限制了 js 的很多功能,比如 js 在瀏覽器引擎下都不能進(jìn)行文件的讀寫告唆,當(dāng)然這么做是為了用戶的安全著想棺弊。如果我們想要用 js 實(shí)現(xiàn) python 的許多功能呢?這時(shí)就需要 node 了擒悬。

先去這里下載 node 模她,像安裝 python 一樣把 node 安裝到你的電腦上,記得把安裝路徑添加到環(huán)境變量中懂牧。這些都是和安裝 python 是一樣的侈净。

python 運(yùn)行 .py 腳本是 python <filename>.py 命令,node 也是同理僧凤, node <filename>.js 就可以運(yùn)行一個(gè) js 腳本了畜侦。

在上一章,我們在寫 index.js 時(shí)需要考慮代碼編寫的順序躯保,這是一件煩人的事情旋膳。等到以后代碼量大起來,誰知道哪個(gè)組件引用了哪個(gè)組件途事,還容易出現(xiàn) undefined 錯(cuò)誤验懊。要是我們能單獨(dú)把組件都寫在一個(gè)地方擅羞,要用他們的時(shí)候再按照需求引入就好了。也就是义图,我們希望能夠進(jìn)行“模塊化”開發(fā)减俏。不用去考慮代碼順序,做到代碼解耦歌溉。

js 被創(chuàng)建的時(shí)候并沒有考慮到模塊化開發(fā)垄懂,因?yàn)楫?dāng)時(shí)的需求還是很簡單的,隨著需求變多痛垛,模塊化開發(fā)成了必須草慧。我們知道,我們可以在 python 中使用 import 來引入我們需要的包和庫匙头。 由于在 es6 之前還沒有官方提供這個(gè)功能漫谷,于是 js 社區(qū)就自己實(shí)現(xiàn)了這項(xiàng)需求。這就是 requiremodule.exports 的故事蹂析,也就是 CommonJS 規(guī)范舔示。

在 python 中,我們直接使用 import 就可以從一個(gè)包中直接導(dǎo)入我們需要的東西电抚。但是 js 有些不同惕稻,js 需要被導(dǎo)入的包主動導(dǎo)出內(nèi)部變量,然后其它的包才能導(dǎo)入他們蝙叛。

在 CommonJS 規(guī)范中俺祠,每一個(gè)模塊都默認(rèn)含有一個(gè)全局變量 module ,它有一個(gè) exports 屬性借帘,我們可以通過這個(gè)屬性來向外暴露內(nèi)部的變量蜘渣。module.exports 的默認(rèn)值為一個(gè)空對象。外部可以通過全局的 require 函數(shù)來導(dǎo)入其它包內(nèi)的 module.exports 變量肺然。

// A.js
function out(){
    console.log('model A.')
} 
 
module.exports = out // 導(dǎo)出 out 函數(shù)

// B.js
const out = require('./A') // 從 A.js 引入 out
out()

在終端里輸入 node B.js 蔫缸,你就會看到控制臺打印出了 model A.

就這么簡單际起。和 Python 的差別就是 js 需要你主動導(dǎo)出變量拾碌。這也是 node 引用模塊的方法。

如果你不想寫 module.exports 加叁,還有另外一個(gè)全局變量 exports 供你使用倦沧,它是 module.exports引用,由于 module.exports 的默認(rèn)值為一個(gè)空對象它匕,所以它的默認(rèn)值也是一個(gè)空對象展融。如:

// A.js
exports.a = 'a';
exports.b = function(){
    console.log('b')
}

// B.js
const A = require('./A')
console.log(A.a) // 'a'
A.b() // 'b'

有時(shí)候我們的模塊不止一個(gè)文件,而是有很多個(gè)文件豫柬。我們可以直接使用 require 來引入模塊路徑告希,require 會自動搜尋引入目錄下的 index.js 文件扑浸,它會把這個(gè)文件作為整個(gè)模塊的入口。如:

// 模塊 ucag
ucag/
    index.js // module.exports = {
        name: require('./name').name,
        age: require('./age').age,
        job: require('./job').job
    }

    age.js // exports.age = 18
    name.js // exports.name = 'ucag'
    job.js // exports.job = 'student'

我們在一個(gè)文件里引入:

const ucag = require('./ucag')
ucag.name // 'ucag'
ucag.age // 18
ucag.job // 'student'

在 es6 之后燕偶,js 有了自己引用模塊的方法喝噪,它有了自己的 importexport 關(guān)鍵字。對外導(dǎo)出用 export 指么,對內(nèi)引入用 import酝惧。

對于導(dǎo)出,需要遵循以下語法:

export expression
    // 如:
        export var a = 1, b = 2;  // 導(dǎo)出 a 和 b 兩個(gè)變量

export {var1, var2, ...} //var1 var2 為導(dǎo)要出的變量

export { v1 as var1, v2 as var2} // 使用 as 來改變導(dǎo)出變量的名字

不過需要注意的是伯诬,當(dāng)我們只想導(dǎo)出一個(gè)變量時(shí)晚唇,我們不能這么寫:

let a = 1;
export a; // 這是錯(cuò)誤的寫法
export { a } // 這才是正確的寫法

我們可以這樣來引入:

import { var1 }from 'model' // 從 model 導(dǎo)出 var1 變量
import {v1, v2 } from 'model' // 從 model 導(dǎo)出多個(gè)變量
import { var1 as v1 }from 'model'  // 從 model 導(dǎo)出 var1 變量并命名為 v1
import * as NewVar from 'model' // 從 model 導(dǎo)入全部的變量

在使用 import 時(shí),import 的變量名要和 export 的變量名完全相同盗似,但是有時(shí)候我們我們并不知道一個(gè)文件導(dǎo)出的變量叫什么名字哩陕,只知道我們需要使用這個(gè)模塊默認(rèn)導(dǎo)出的東西,于是便出現(xiàn)了 default 關(guān)鍵字的使用赫舒。我們可以在 export 時(shí)使用這個(gè)關(guān)鍵字來做到“匿名”導(dǎo)出悍及,在 import 時(shí),隨便取個(gè)變量名就可以了接癌。

export default expression
    // 如:
    export default class {} // 導(dǎo)出一個(gè)類
    export default {} //導(dǎo)出一個(gè)對象
    export default function(){} //導(dǎo)出一個(gè)函數(shù)

我們可以這樣來引入:

import NewVar from 'model' // NewVar 是我們?yōu)?export default 導(dǎo)出變量取的名字心赶。

注意,默認(rèn)導(dǎo)出和命名導(dǎo)出各自的導(dǎo)入是有區(qū)別的:

// 默認(rèn)導(dǎo)出
export default {
    name:'ucag'
}
// 默認(rèn)導(dǎo)出對應(yīng)導(dǎo)入
import AnyVarName from 'model' // 沒有花括號
AnyVarName.name // 'ucag'

//命名導(dǎo)出
export var name='ucag'
//命名導(dǎo)出對應(yīng)導(dǎo)入
import { name } from 'model' // 有花括號
name // 'ucag'

//兩種導(dǎo)出方式同時(shí)使用
export default {
    name:'ucag'
}
export var age=18;

//兩種導(dǎo)入
import NameObj from 'model' //導(dǎo)入默認(rèn)導(dǎo)出
import { age } from 'model' //導(dǎo)入命名導(dǎo)出

NameObj.name // 'ucag'
age // 18

總結(jié)一下:

  1. 目前我們學(xué)了兩種模塊化的方式缺猛。他們是 CommonJS 的模塊化方式與 es6 的模塊化方式园担。兩種方式不要混用了哦。
  2. CommonJS 規(guī)范:
    1. 使用 module.exportsexports 來導(dǎo)出內(nèi)部變量
    2. 使用 require 導(dǎo)入變量枯夜。當(dāng)被導(dǎo)入對象是路徑時(shí),require 會自動搜尋并引入目錄下的 index.js 文件艰山,會把這個(gè)文件作為整個(gè)文件的入口湖雹。
  3. es6 規(guī)范:
    1. 使用 importexport 來導(dǎo)出內(nèi)部變量
    2. 當(dāng)導(dǎo)入命名導(dǎo)出變量時(shí),使用基于 import { varName } from 'model' 的語法曙搬;當(dāng)導(dǎo)入匿名或默認(rèn)導(dǎo)入時(shí)摔吏,使用 import varName from 'model' 語法;

悲催的是纵装,node 只支持 CommonJS 方式來進(jìn)行模塊化編寫代碼征讲。

前端的 pip —— npm

剛才我們講了模塊化,現(xiàn)在我們就可以用不同的模塊做很多事情了橡娄。 我們可以使用 pip 來安裝 python 的相關(guān)包诗箍,在 node 下,我們可以使用 npm 來安裝我們需要的庫挽唉。當(dāng)然滤祖,安裝包的工具不止有 npm 一種筷狼,還有許多其它的包管理工具供我們使用。現(xiàn)在的 python 已經(jīng)在安裝時(shí)默認(rèn)安裝了 pip 匠童,node 在安裝時(shí)已經(jīng)默認(rèn)安裝了 npm 埂材,所以我們就用這個(gè)現(xiàn)成的工具。

前端項(xiàng)目有個(gè)特點(diǎn) —— 版本更替特別快汤求。今天頁面是一個(gè)樣子俏险,明天可能就換成另外的樣子了,變化特別頻繁扬绪,可能今天的依賴庫是一個(gè)較低的版本竖独,明天它就更新了。所以需要把依賴的庫和項(xiàng)目放在一起勒奇,而不是全局安裝到 node 環(huán)境中预鬓。每開發(fā)一個(gè)新項(xiàng)目就需要重新安裝一次依賴庫。而真正的 node 環(huán)境下可能是什么都沒有的赊颠,就一個(gè) npm 格二。

在一個(gè)前端項(xiàng)目中,總是會把依賴庫放進(jìn)一個(gè)文件夾里竣蹦,然后從這個(gè)文件夾里導(dǎo)入需要的庫和依賴顶猜,這個(gè)文件夾叫做 node_modules

在 pip 中痘括,我們可以使用 requirements.txt 來記錄我們的項(xiàng)目依賴长窄。在 npm 下,我們使用 package.json 來記錄依賴纲菌。當(dāng)我們在 package.json 中寫好需要的依賴后挠日,在同一路徑下運(yùn)行 npm install, npm 會自動搜尋當(dāng)前目錄下的 package.json 并且自動安裝其中的依賴到 node_modules 中翰舌,要是當(dāng)前目錄沒有 node_modules 目錄嚣潜,npm 就會幫我們自己創(chuàng)建一個(gè)。當(dāng)我們想要使用別人的項(xiàng)目時(shí)椅贱,直接把他們的 package.json 拷貝過來懂算,再 npm install 就可以完成開發(fā)環(huán)境的搭建了。這樣是不是特別的方便庇麦。

當(dāng)你在運(yùn)行完了 npm install 時(shí)计技,如果在以后的開發(fā)中想要再安裝新的包,直接使用 npm install <your-package> 安裝新的包就行了山橄,npm 會自動幫你把新的包裝到當(dāng)前的 node_modules 下垮媒。

在我們發(fā)布一個(gè) python 項(xiàng)目時(shí),我們對于依賴的說明通常是自己寫一個(gè) requirements.txt ,讓用戶們自己去裝依賴涣澡。 npm 為我們提供了更加炫酷的功能贱呐。在開發(fā)項(xiàng)目時(shí),你直接在含有 package.json 的目錄下運(yùn)行 npm install <your-package> --save-dev 入桂,npm 會自動幫你把依賴寫到 package.json 中奄薇。以后你就可以直接發(fā)布自己的項(xiàng)目,都不用在 package.json 中手寫依賴抗愁。

通過上面的內(nèi)容我們知道馁蒂,我們只需要在一個(gè)文件夾中創(chuàng)建好 package.json ,就可以自動安裝我們的包了蜘腌。 我們還可以使用 npm 自動生成這個(gè)文件沫屡。在一個(gè)空目錄下,運(yùn)行 npm init 撮珠,npm 會問你一些有的沒的問題沮脖,你可以隨便回答,也可以一路回車什么都不答芯急,目錄下就會自動多一個(gè) package.json 文件勺届。比如我們在一個(gè)叫做 vue-test 的路徑下運(yùn)行這個(gè)命令,記得以管理員權(quán)限運(yùn)行娶耍。

λ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (vue-test)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\Users\Administrator\Desktop\vue-test\package.json:

{
  "name": "vue-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes)

如果你不想按回車免姿,在運(yùn)行 npm init 時(shí)加一個(gè) -y 參數(shù),npm 就會默認(rèn)你使用它生成的答案榕酒。也就是運(yùn)行 npm init -y 就行了胚膊。

λ npm init -y
Wrote to C:\Users\Administrator\Desktop\vue-test\package.json:

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

然后在以后的安裝中,我們使用 npm install --save-dev 想鹰,就會自動把依賴庫安裝到 node_modules 中紊婉,把相關(guān)庫依賴的版本信息寫入到 package.json 中。

還是以剛才的 vue-test 為例辑舷,在創(chuàng)建完了 package.json 后肩榕,運(yùn)行:

λ npm install --save-dev jquery
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN vue-test@1.0.0 No description
npm WARN vue-test@1.0.0 No repository field.

+ jquery@3.2.1
added 1 package in 5.114s

此時(shí),我們發(fā)現(xiàn)又多了一個(gè) package-lock.json文件惩妇, 先不管它。我們再打開 package.json 看看筐乳,你會發(fā)現(xiàn)它的內(nèi)容變成了這樣:

λ cat package.json
{
  "name": "vue-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jquery": "^3.2.1"
  }
}

依賴已經(jīng)自動寫入了 package.json 中歌殃。我們再刪除 node_modules 文件夾和 package-lock.json ,只留下 package.json 蝙云。再運(yùn)行 npm install氓皱。

λ npm install
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN vue-test@1.0.0 No description
npm WARN vue-test@1.0.0 No repository field.

added 1 package in 5.4s

我們發(fā)現(xiàn) npm 已經(jīng)為我們安裝好了依賴。

當(dāng)然,我們有時(shí)需要一些各個(gè)項(xiàng)目都會用到的工具波材。還是以 python 為例股淡,我們會使用 virtualenv 來創(chuàng)建虛擬環(huán)境,在安裝它時(shí)廷区,我們直接全局安裝到了系統(tǒng)中唯灵。npm 也可以全局安裝我們的包。在任意路徑下隙轻,使用 npm install -g <your-package> 就可以全局安裝一個(gè)包了埠帕。我們在以后會用到一個(gè)工具叫做 vue-cli ,我們可以用它來快速的創(chuàng)建一個(gè) vue 項(xiàng)目玖绿。為什么要用它呢敛瓷,在前端項(xiàng)目中,有一些庫是必須要用到的比如我們的 webpack 斑匪,比如開發(fā) vue 需要用到的 vue 包呐籽,vue-routervuex 等等蚀瘸,它會幫我們把這些自動寫入 package.json 中狡蝶,并且會為我們建立起最基本的項(xiàng)目結(jié)構(gòu)。就像是我們使用 django-admin 來創(chuàng)建一個(gè) Django 項(xiàng)目一樣苍姜。這樣的工具牢酵,在前端被稱為“腳手架”

任意路徑下運(yùn)行:

npm install -g vue-cli

vue 腳手架就被安裝到了我們的 node 環(huán)境中衙猪。我們就可以在命令行中使用 vue 命令來創(chuàng)建新的項(xiàng)目了馍乙,不需要自己手動創(chuàng)建項(xiàng)目。大家可以試著運(yùn)行 vue --help 垫释,看看你是否安裝成功了 vue-cli丝格。

λ vue --help

  Usage: vue <command> [options]


  Options:

    -V, --version  output the version number
    -h, --help     output usage information


  Commands:

    init        generate a new project from a template
    list        list available official templates
    build       prototype a new project
    help [cmd]  display help for [cmd]

npm 除了可以安裝包之外,還可以使用 npm run 用來管理腳本命令棵譬。
還是以剛才安裝 'jquery' 的包為例灼捂,打開 package.json ,把 scripts 字段改成這樣:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "vh": "vue --help"
  }

然后在 package.json 路徑下運(yùn)行 npm run vh 职祷,你就會看到控制臺輸出了 vue 腳手架的幫助信息盟榴。

當(dāng)我們運(yùn)行 npm run <cmd>時(shí),npm 會搜尋同目錄下的 package.json 中的 scripts 中對應(yīng)的屬性脏嚷,然后把當(dāng)前的 node_modules 加入環(huán)境變量中骆撇,執(zhí)行其中命令,這樣就不用我們每次都都手動輸入長長的命令了父叙。

還是總結(jié)一下:

  1. npm 是 node 中的包管理工具神郊。
  2. npm install: 安裝 package.json 中的依賴到 node_modules 中肴裙。
  3. npm install <package-name> --save-dev:把包安裝到 node_modules 中,并把包依賴寫入 package.json 中涌乳。
  4. npm install <package-name> -g:全局安裝某個(gè)包蜻懦。
  5. npm run <cmd>: 運(yùn)行當(dāng)前目錄下 package.jsonscripts 中的命令。

前端工具鏈

前端開發(fā)會用到許許多多的工具夕晓,有的工具是為了更加方便的開發(fā)而生宛乃,有的工具是為了使代碼更好的適應(yīng)瀏覽器環(huán)境。每個(gè)工具的出現(xiàn)都是為了解決特定的問題运授。

解決版本差異 —— babel

版本差異一直是個(gè)很讓人頭痛的問題烤惊。用 python2 寫的代碼,大概率會在 python3 上運(yùn)行失敗吁朦。 js 是運(yùn)行在瀏覽器上的柒室,很多的瀏覽器更新并沒有能夠很穩(wěn)定的跟上 js 更新的步伐,有的瀏覽器只支持到 es5 逗宜,或者只支持部分 es6 特性雄右。為了能夠向下兼容,大家想了辦法 —— 把 es6 的代碼轉(zhuǎn)換為 es5 的代碼就行了纺讲!開發(fā)的時(shí)候用 es6 擂仍,最后再把代碼轉(zhuǎn)換成 es5 代碼就行了!于是便出現(xiàn)了 babel 熬甚。

創(chuàng)建一個(gè)叫做 babel-test 的文件夾逢渔,在此路徑下運(yùn)行:

npm init -y
npm install --save-dev babel-cli

在使用 babel 前,我們需要通過配置文件告訴它轉(zhuǎn)碼規(guī)則是什么乡括。babel 默認(rèn)的配置文件名為 .babelrc肃廓。
babel-test 下創(chuàng)建 .babelrc,寫入:

  {
    "presets": [
      "es2015"
    ],
    "plugins": []
  }

轉(zhuǎn)碼規(guī)則是以附加規(guī)則包的形式出現(xiàn)的诲泌。所以在配置好了之后我們還需要安裝規(guī)則包盲赊。

npm install --save-dev babel-preset-es2015

babel 是以命令行的形式使用的,最常用的幾個(gè)命令如下:

# 轉(zhuǎn)碼結(jié)果打印到控制臺
 babel es6.js

# 轉(zhuǎn)碼結(jié)果寫入一個(gè)文件
 babel es6.js -o es5.js # 將 es6.js 的轉(zhuǎn)碼結(jié)果寫入 es5.js 中

# 轉(zhuǎn)碼整個(gè)目錄到指定路徑
 babel es6js -d es5js # 將 es6js 路徑下的 js 文件轉(zhuǎn)碼到 es5js 路徑下

但是由于我們的 babel 是安裝在 babel-test 的 node_modules 中的敷扫,所以需要使用 npm run 來方便運(yùn)行以上命令哀蘑。

編輯 package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "babel inputs -d outputs"
  }

在 babel-test 下創(chuàng)建一個(gè)新的目錄 inputs ,在其中寫入新的文件 a.js:

// es6 語法葵第,模板字符串
let name = 'ucag'
let greeting = `hello my name is ${name}`

然后運(yùn)行:

npm run build

在轉(zhuǎn)換完成之后绘迁,我們在 outputs 下找到 a.js,發(fā)現(xiàn)代碼變成了這樣:

'use strict';

var name = 'ucag';
var greeting = 'hello my name is ' + name;

es6 代碼已經(jīng)被轉(zhuǎn)換為了 es5 代碼卒密。

整合資源 —— webpack

在一個(gè)前端項(xiàng)目中缀台,會有許許多多的文件。更重要的是栅受,最后我們需要通過瀏覽器來運(yùn)行他們将硝。我們用 es6 寫的代碼,需要轉(zhuǎn)換一次之后才能上線使用屏镊。如果我們用的是 TypeScript 寫的 js 依疼,那我們還需要先把 TypeScript 轉(zhuǎn)換為原生 js ,再用 babel 轉(zhuǎn)換為 es5 代碼而芥。如果我們使用的是模塊化開發(fā)律罢,但是瀏覽器又不支持,我們還需要把模塊化的代碼整合為瀏覽器可執(zhí)行的代碼棍丐∥蠹總之,為了方便開發(fā)與方便在瀏覽器上運(yùn)行歌逢,我們需要用到許許多多的工具巾钉。

webpack 最重要的功能就是可以把相互依賴的模塊打包。我們在模塊化開發(fā)之后秘案,可能會產(chǎn)生許多的 js 文件砰苍,要是一個(gè)個(gè)手寫把他們引入到 html 中是一件很麻煩的事情,所以我們此時(shí)就需要 webpack 來幫我們把分離的模塊組織到一起阱高,這樣就會方便很多了
創(chuàng)建一個(gè)新的路徑 webpack-test 赚导,在此路徑下運(yùn)行:

npm init -y
npm install --save-dev webpack

使用前先配置,在配置之前我們需要知道一些最基本的概念赤惊。

  1. 入口 entry:不管一個(gè)項(xiàng)目有多復(fù)雜吼旧,它總是有一個(gè)入口的。這個(gè)入口就被稱為 entry 未舟。這就像是我們的模塊有個(gè) index.js 一樣圈暗。
  2. 出口 output:webpack 根據(jù)入口文件將被依賴的文件按照一定的規(guī)則打包在一起,最終需要一個(gè)輸出打包文件的地方处面,這就是 output 厂置。

這就是最基本的概念了,我們會在以后的教程中學(xué)習(xí)到更多有關(guān) webpack 配置的知識魂角,不過由于我們目前的需求還很簡單昵济,還用不到其它的一些功能,就算是現(xiàn)在講了也難以體會其中的作用野揪,所以我們目前不著急访忿,慢慢來。

webpack 有多種加載配置的方法斯稳,一種是寫一個(gè)獨(dú)立的配置文件海铆,一種是在命令行內(nèi)編寫配置,還有許多其它更靈活編寫配置的方法挣惰,我們以后再說卧斟。當(dāng)我們在 webpack-test 下不帶任何參數(shù)運(yùn)行 webpack 命令時(shí)殴边,webpack 會自動去尋找名為 webpack.config.js 的文件,這就是它默認(rèn)的配置文件名了珍语。

在 webpack-test 下創(chuàng)建一個(gè)新的文件 webpack.config.js:

module.exports = {
    entry:'./main.js', // 入口文件為當(dāng)前路徑下的 main.js 為文件
    output:{
        path:__dirname, // __dirname 是 node 中的全局變量锤岸,代表當(dāng)前路徑。
        filename:'index.js' // 打包之后的文件名
    }
}

編輯 package.json

"scripts"{
    'pkg':'webpack' // 編輯快捷命令
}

以第三章的 index.js 為例板乙,當(dāng)時(shí)我們把所有的代碼都寫到了一個(gè)文件中是偷,現(xiàn)在我們可以把他們分開寫了,最后再打包起來募逞。

創(chuàng)建幾個(gè)新文件分別為 components.js api.js store.js main.js vue.js jquery.js

vue.js: vue 源文件
jquery: jquery 源文件

api.js:

let api = {
    v1: {
        run: function () {
            return '/api/v1/run/'
        },
        code: {
            list: function () {
                return '/api/v1/code/'
            },
            create: function (run = false) {
                let base = '/api/v1/code/';
                return run ? base + '?run' : base
            },
            detail: function (id, run = false) {
                let base = `/api/v1/code/${id}/`;
                return run ? base + '?run' : base
            },
            remove: function (id) {
                return api.v1.code.detail(id, false)
            },
            update: function (id, run = false) {
                return api.v1.code.detail(id, run)
            }
        }
    }
}

module.exports = api // 導(dǎo)出 API 

store.js

const $ = require('./jquery') // 引入 jquery 

let store = {
    state: {
        list: [],
        code: '',
        name: '',
        id: '',
        output: ''
    },
    actions: {
        run: function (code) { //運(yùn)行代碼
            $.post({
                url: api.v1.run(),
                data: {code: code},
                dataType: 'json',
                success: function (data) {
                    store.state.output = data.output
                }
            })
        },
        runDetail: function (id) { //運(yùn)行特定的代碼
            $.getJSON({
                url: api.v1.run() + `?id=${id}`,
                success: function (data) {
                    store.state.output = data.output
                }
            })
        },
        freshList: function () { //獲得代碼列表
            $.getJSON({
                url: api.v1.code.list(),
                success: function (data) {
                    store.state.list = data
                }
            })
        },
        getDetail: function (id) {//獲得特定的代碼實(shí)例
            $.getJSON({
                url: api.v1.code.detail(id),
                success: function (data) {
                    store.state.id = data.id;
                    store.state.name = data.name;
                    store.state.code = data.code;
                    store.state.output = '';
                }
            })
        },
        create: function (run = false) { //創(chuàng)建新代碼
            $.post({
                url: api.v1.code.create(run),
                data: {
                    name: store.state.name,
                    code: store.state.code
                },
                dataType: 'json',
                success: function (data) {
                    if (run) {
                        store.state.output = data.output
                    }
                    store.actions.freshList()
                }
            })
        },
        update: function (id, run = false) { //更新代碼
            $.ajax({
                url: api.v1.code.update(id, run),
                type: 'PUT',
                data: {
                    code: store.state.code,
                    name: store.state.name
                },
                dataType: 'json',
                success: function (data) {
                    if (run) {
                        store.state.output = data.output
                    }
                    store.actions.freshList()
                }
            })
        },
        remove: function (id) { //刪除代碼
            $.ajax({
                url: api.v1.code.remove(id),
                type: 'DELETE',
                dataType: 'json',
                success: function (data) {
                    store.actions.freshList()
                }
            })
        }
    }
}

store.actions.freshList() // Store的初始化工作蛋铆,先獲取代碼列表

module.exports = store // 導(dǎo)出 store

components.js

const store = require('./store')
let list = { //代碼列表組件
    template: `
    <table class="table table-striped">
        <thead> 
            <tr>
                <th class="text-center">文件名</th> 
                <th class="text-center">選項(xiàng)</th> 
            </tr>
        </thead>
        <tbody>
            <tr v-for="item in state.list">
            <td class="text-center">{{ item.name }}</td>
            <td>
                <button class='btn btn-primary' @click="getDetail(item.id)">查看</button>
                <button class="btn btn-primary" @click="run(item.id)">運(yùn)行</button>
                <button class="btn btn-danger" @click="remove(item.id)">刪除</button>
            </td>
            </tr>
        </tbody> 
    </table>
    `,
    data() {
        return {
            state: store.state
        }
    },
    methods: {
        getDetail(id) {
            store.actions.getDetail(id)
        },
        run(id) {
            store.actions.runDetail(id)
        },
        remove(id) {
            store.actions.remove(id)
        }
    }
}
let options = {//代碼選項(xiàng)組件
    template: `
    <div style="display: flex;
         justify-content: space-around;
         flex-wrap: wrap" >
        <button class="btn btn-primary" @click="run(state.code)">運(yùn)行</button>
        <button class="btn btn-primary" @click="update(state.id)">保存</button>
        <button class="btn" @click="update(state.id, true)">保存并運(yùn)行</button>
        <button class="btn btn-primary" @click="newOptions">New</button>
    </div>
    `,
    data() {
        return {
            state: store.state
        }
    },
    methods: {
        run(code) {
            store.actions.run(code)
        },
        update(id, run = false) {
            if (typeof id == 'string') {
                store.actions.create(run)
            } else {
                store.actions.update(id, run)
            }
        },
        newOptions() {
            this.state.name = '';
            this.state.code = '';
            this.state.id = '';
            this.state.output = '';
        }
    }
}
let output = { //代碼輸出組件
    template: `
    <textarea disabled 
    class="form-control text-center">{{ state.output }}</textarea>
    `,
    data() {
        return {
            state: store.state
        }
    },
    updated() {
        let ele = $(this.$el);
        ele.css({
            'height': 'auto',
            'overflow-y': 'hidden'
        }).height(ele.prop('scrollHeight'))
    }
}
let input = { //代碼輸入組件
    template: `
    <div class="form-group">
        <textarea 
        class="form-control" 
        id="input"
        :value="state.code"
        @input="inputHandler"></textarea> 
        <label for="code-name-input">代碼片段名</label>
        <p class="text-info">如需保存代碼,建議輸入代碼片段名</p>
        <input 
        type="text" 
        class="form-control" 
        :value="state.name"
        @input="(e)=> state.name = e.target.value">
    </div>
    `,
    data() {
        return {
            state: store.state
        }
    },
    methods: {
        flexSize(selector) {
            let ele = $(selector);
            ele.css({
                'height': 'auto',
                'overflow-y': 'hidden'
            }).height(ele.prop('scrollHeight'))
        },
        inputHandler(e) {
            this.state.code = e.target.value;
            this.flexSize(e.target)
        }
    }
}

module.exports = {
    list, input, output, options
} // 導(dǎo)出組件

main.js

const cmp = require('./components') //引入組件
const list = cmp.list
const options = cmp.options
const input = cmp.input
const output = cmp.output
const Vue = require('./vue')

let app = { //整體頁面布局
    template: `
        <div class="continer-fluid">
            <div class="row text-center h1">
                在線 Python 解釋器
            </div>
            <hr>
            <div class="row">
                <div class="col-md-3">
                    <code-list></code-list>
                </div>
                <div class="col-md-9">
                    <div class="container-fluid">
                        <div class="col-md-6">
                            <p class="text-center h3">請?jiān)谙路捷斎氪a:</p>
                            <code-input></code-input>
                            <hr>
                            <code-options></code-options>
                        </div>
                        <p class="text-center h3">輸出</p>
                        <div class="col-md-6">
                            <code-output></code-output>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    `,
    components: {
        'code-input': input,
        'code-list': list,
        'code-options': options,
        'code-output': output
    }
}

let root = new Vue({ //根組件放接,整個(gè)頁面入口
    el: '#app',
    template: '<app></app>',
    components: {
        'app': app
    }
})

在 webpack-test 下運(yùn)行:

npm run pkg # 運(yùn)行 webpack

過一會兒你就會發(fā)現(xiàn)多了一個(gè) index.js 文件刺啦,這就是我們打包的最終結(jié)果了。 6 個(gè) js 文件被打包成了一個(gè)文件透乾,最終打包的 index.js 的功能和那 6 個(gè) js 文件的功能都是一樣的洪燥。并且瀏覽器可以正常執(zhí)行這些代碼, webpack 已經(jīng)為我們整合好代碼乳乌,瀏覽器中不會出現(xiàn)模塊化支持的問題捧韵。

要是你把 index.js 放到我們的 index.html 里,先不引入 bootstrap 汉操,你會發(fā)現(xiàn)頁面還是可以正常使用的再来。功能和前面完全相同!如果我們不使用 webpack 磷瘤,那么我們需要在 html 頁面按照順序挨著寫 <script src=""></script> 芒篷。


本章前端工具鏈的部分就簡單的介紹到這里。我們并沒有打包 bootstrap采缚,要是你把 bootstrap.js 和我們的代碼打包到一起针炉,你會發(fā)現(xiàn)它在控制臺報(bào)錯(cuò)了。這是 webpack 的問題嗎扳抽?這是我們之后要解決的問題篡帕。保持你的好奇心。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贸呢,一起剝皮案震驚了整個(gè)濱河市镰烧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌楞陷,老刑警劉巖怔鳖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異固蛾,居然都是意外死亡结执,警方通過查閱死者的電腦和手機(jī)度陆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來献幔,“玉大人坚芜,你說我怎么就攤上這事⌒崩眩” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵沧竟,是天一觀的道長铸敏。 經(jīng)常有香客問我,道長悟泵,這世上最難降的妖魔是什么杈笔? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮糕非,結(jié)果婚禮上蒙具,老公的妹妹穿的比我還像新娘。我一直安慰自己朽肥,他們只是感情好禁筏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衡招,像睡著了一般篱昔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上始腾,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天州刽,我揣著相機(jī)與錄音,去河邊找鬼浪箭。 笑死穗椅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奶栖。 我是一名探鬼主播匹表,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼驼抹!你這毒婦竟也來了桑孩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤框冀,失蹤者是張志新(化名)和其女友劉穎流椒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體明也,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宣虾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年惯裕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绣硝。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜻势,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹉胖,到底是詐尸還是另有隱情握玛,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布甫菠,位于F島的核電站挠铲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏寂诱。R本人自食惡果不足惜拂苹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痰洒。 院中可真熱鬧瓢棒,春花似錦、人聲如沸丘喻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泉粉。三九已至嗅绰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搀继,已是汗流浹背窘面。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叽躯,地道東北人财边。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像点骑,于是被迫代替她去往敵國和親酣难。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353