零、前端技術(shù)選型
1、多頁應(yīng)用
① 特點(diǎn):
- 內(nèi)容都是由服務(wù)端用模板生成首昔,如JSP敢课,Django等
- 每次頁面跳轉(zhuǎn)都要經(jīng)過服務(wù)端旬盯,會給用戶一定等待時間
- JavaScript更多只是做頁面的事件、動畫等
② 主要的類庫:
- jQuery(快速操作DOM翎猛,兼容各類瀏覽器)
- YUI
③ 架構(gòu)工具:
- 沒有特定的前端工具胖翰,跟后端配合
- grunt / gulp
④ 模塊化工具:
seajs
requirejs
⑤ 靜態(tài)文件
使用gulp或grunt等工作手動編譯到html中,自由度低切厘,操作復(fù)雜萨咳,或者不處理,直接交給后端疫稿,讓后端處理培他。
2、單頁應(yīng)用
① 特點(diǎn):
- 所有內(nèi)容都在前端生成
- JavaScript承擔(dān)更多的業(yè)務(wù)邏輯遗座,后端只提供API
- 頁面路由跳轉(zhuǎn)不需要經(jīng)過后端舀凛,由前端生成路由
② 常用類庫:
- Backbone.js
- Angular.js(typescript, .ts,http2)
- React.js(.jsx途蒋,單向數(shù)據(jù)流猛遍,方便數(shù)據(jù)管理)
- Vue.js(.vue, template,數(shù)據(jù)雙向綁定)
③ 架構(gòu)工具
- npm包管理器
- bower
- jspm(前端類庫單獨(dú)出來号坡,使用http2懊烤,效率更高)
④ 模塊化工具
- webpack
- rollup(按需加載,打包更快)
- browserify
⑤ 靜態(tài)文件
可以直接在JavaScript代碼中進(jìn)行引用宽堆,并且交由模塊化工具轉(zhuǎn)化成線上可用的靜態(tài)資源腌紧,可以定制轉(zhuǎn)化過程適應(yīng)不同的需求場景。
⑥ 其他考慮因素
- 瀏覽器兼容性
- 移動端還是PC端
- 面向市場用戶(重交互畜隶,性能)壁肋,還是面向企業(yè)使用(重安全,功能)
一籽慢、為什么要使用工程架構(gòu)浸遗?
① 極大地解放了生產(chǎn)力,提高了開發(fā)效率
- 源代碼預(yù)處理嗡综,將項(xiàng)目框架語言最終轉(zhuǎn)化為瀏覽器能看懂的JavaScript
- 自動打包乙帮,自動更新頁面顯示
- 自動圖片處理依賴,保證開發(fā)和正式環(huán)境的統(tǒng)一
② 圍繞解決方案搭建環(huán)境
- 不同的前端框架需要不同的運(yùn)行架構(gòu)
- 語氣可能出現(xiàn)的問題并規(guī)避
③ 保證項(xiàng)目質(zhì)量
- 代碼規(guī)范极景,使用Eslint配置
- 不同操作系統(tǒng)或編輯器下差異的排除察净,使用editorconfig配置
- git commit預(yù)處理驾茴,提交代碼前,強(qiáng)制執(zhí)行代碼規(guī)范
二氢卡、webpack基本配置
1锈至、什么是webpack?
Webpack is a module bundler for modern JavaScript applications.
webpack官方介紹译秦,它是一個現(xiàn)代JavaScript應(yīng)用誕生的模塊打包器峡捡。
2、基礎(chǔ)配置
// webpack.config.client.js
const path = require('path');
module.exports = {
entry: { // (1)
app: path.join(__dirname, '../client/app.js'),
},
output: { // (2)
filename: '[name].[hash:5].js', // (3)
path: path.join(__dirname, '../dist'), // (4)
publicPath: '/public' // (5)
}
}
- (1)
enrty
是指定了一個資源文件的路徑 - (2)
output
指定了輸出文件的目錄 - (3)
filename
是輸出文件名筑悴,可以設(shè)置帶有hash值的名稱们拙,方便文件修改時文件名改變,避免瀏覽器緩存帶來的不必要麻煩阁吝。 - (4)
path
用來存放打包后文件的輸出目錄 - (5)
publicPath
用來配置發(fā)布到線上的URL前綴砚婆,默認(rèn)值為' ',即使用相對路徑突勇。
當(dāng)需要構(gòu)建出的資源文件上傳到CDN服務(wù)商時候装盯,為了加快頁面的打開速度,需要如下配置:
filename: '[name]_[chunkhash:5].js',
publicPath: 'https://cdn.example.com/assets/'
此時發(fā)布到線上的HTML在引入JavaScript文件時甲馋,就需要如下配置項(xiàng):
<script src="https://cdn.example.com/assets/a_12345.js"></script>
3埂奈、loader基礎(chǔ)應(yīng)用
// webpack.config.client.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: path.join(__dirname, '../client/app.js'),
},
output: {
filename: '[name].[hash:5].js',
path: path.join(__dirname, '../dist'),
publicPath: '',
},
module: {
rules: [
{
test: /.jsx$/,
loader: 'babel-loader', // (1)
},
{
test: /.js$/,
loader: 'babel-loader',
exclude: [ // (2)
path.join(__dirname, '../node_modules')
]
}
]
},
plugins: [
new HtmlWebpackPlugin() // (3)
]
}
- (1) 當(dāng)頁面需要引入一些非原生JavaScript的模塊(module)時,需要在module中設(shè)置各種loader進(jìn)行操作定躏,方便瀏覽器識別账磺,如對react的
.jsx
文件進(jìn)行支持,需要引入babel-loader
- (2) 不需要對node_modules中的js進(jìn)行編譯共屈,則需要用
exclude
進(jìn)行排除绑谣,同時需要設(shè)置在項(xiàng)目根目錄設(shè)置.babelrc
,如下拗引,對es6和react進(jìn)行編譯和寬松的支持。
// .babelrc
{
"presets": [
["es2015", {"loose": true}],
"react"
]
}
- (3) webpack 通過
plugins
安裝插件類實(shí)現(xiàn)一些功能幌衣,常用的如矾削,HtmlWebpackPlugin
,它依據(jù)一個簡單的模板豁护,生成最終的html文件哼凯,這個文件中自動引用了打包后的JS文件。每次編譯都在文件名中插入一個不同的帶有哈希值的JS文件楚里。
三断部、服務(wù)端渲染基礎(chǔ)配置
1、為什么需要服務(wù)端渲染(SSR:Server-Side-Render)
- (1) SEO不友好班缎,單頁應(yīng)用在瀏覽器端渲染HTML頁面蝴光,搜索引擎不會去執(zhí)行JS代碼她渴,不便于搜索引擎的錄入
- (2) 用戶體驗(yàn)不好,頁面渲染每次要等待JS加載完成之后才出現(xiàn)蔑祟,首次請求等待時間較長趁耗,會出現(xiàn)一個短暫的白屏。
2疆虚、React中怎么使用服務(wù)端渲染
react-dom
是React專門為web端開發(fā)的渲染工具苛败,可以在客戶端用react-dom的render方法渲染組件,而服務(wù)端径簿,react-dom/server
提供我們將react組件渲染成HTML的方法罢屈。
// 入口文件app.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.hydrate(<App />, document.getElementById('root')); // (1)
- (1) hydrate 描述的是 ReactDOM 復(fù)用 ReactDOMServer 服務(wù)端渲染的內(nèi)容時盡可能保留結(jié)構(gòu),并補(bǔ)充事件綁定等 Client 特有內(nèi)容的過程篇亭。
服務(wù)端渲染時使用Nodejs儡遮,Nodejs是沒有document或者window對象的,需要創(chuàng)建一個特定的js文件(server-entry.js)去導(dǎo)出要在服務(wù)端渲染的內(nèi)容暗赶,如下:
// server-entry.js
import React from 'react';
import App from 'App.jsx';
export default <App />;
因?yàn)檫@個server-entry.js
需要在Nodejs中執(zhí)行鄙币,所以導(dǎo)出的jsx格式是需要webpack打包編譯的,所以需要再建一個webpack配置文件蹂随,如下:
// webpack.config.server.js
const path = require('path');
module.exports = {
target: 'node', // (1)
entry: {
app: path.join(__dirname, '../client/server-entry.js'),
},
output: {
filename: 'server-entry.js', // (2)
path: path.join(__dirname, '../dist'),
publicPath: '/public',
libraryTarget: 'commonjs2', // (3)
},
module: {
rules: [
{
test: /.jsx$/,
loader: 'babel-loader',
},
{
test: /.js$/,
loader: 'babel-loader',
exclude: [
path.join(__dirname, '../node_modules')
]
}
]
},
// (4)
}
與webpack.config.client.js基本相同十嘿,個別地方做了一些特定的改動:
(1) target: 打包出來的內(nèi)容執(zhí)行環(huán)境設(shè)置,這里因?yàn)樾枰虬蠓诺椒?wù)端Nodejs中執(zhí)行,所以選擇
node
岳锁。還可以是web
绩衷,就是在瀏覽器端執(zhí)行。(2) 因?yàn)榉?wù)端不會有緩存一說激率,所以不需要對壓縮后的文件名進(jìn)行hash處理咳燕,所以打包后的文件名不變。
(3) libraryTarget:設(shè)置打包出來的js模塊方案乒躺,此處使用commonjs2規(guī)范招盲,即最新的commonjs最新的模塊加載方案,適用于Nodejs端嘉冒。還可以設(shè)置
amd
曹货,cmd
等。(4) 同時刪除了HtmlWebpackPlugin讳推,因?yàn)椴恍枰?wù)端渲染時顶籽,不需要再生成一個Html文件了。
此時银觅,需要對package.json中的scripts做一些修改:
"scripts": {
"build:client": "webpack --config build/webpack.config.client.js",
"build:server": "webpack --config build/webpack.config.server.js",
"clear": "rimraf dist", // (1)
"build": "npm run clear && npm run build:client && npm run build:server",// (2)
"start": "node server/server.js"
},
(1)
rimraf
是nodejs一個專門用來刪除文件夾的包礼饱,這里是在每次打包壓縮新的dist文件之前把之前的dist文件刪除(2) build命令把build:client和build:server兩個命令都去執(zhí)行一遍
在client文件夾中新增一個t模板emplate.html文件,同時在webpack.config.client,js
中添加如下配置,將模板template.html合到導(dǎo)出后的index.html中镊绪。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"><app></app></div>
</body>
</html>
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '../client/template.html')
})
]
當(dāng)打包完成后匀伏,在服務(wù)端就可以開始渲染server-entry.js了:
使用Node.js的Express框架,來做服務(wù)端的渲染工作:
const express = require('express');
const ReactSSR = require('react-dom/server');
const serverEntry = require('../dist/server-entry.js').default; (1)
const fs = require('fs');
const path = require('path');
const app = express();
const template = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf8');
app.use('/public', express.static(path.join(__dirname, '../dist'))); // (2)
app.get('*', function(req, res) {
const appString = ReactSSR.renderToString(serverEntry); // (3)
res.send(template.replace('<app></app>', appString)); // (4)
});
app.listen(3333, function(){
console.log('server is running! Visit http://localhost:3333');
})
- (1)
const serverEntry = require('../dist/server-entry.js').default;
這里結(jié)尾之所以要加上.default镰吆,是因?yàn)橛胷equire引入的模塊是整個內(nèi)容帘撰,和import不同,當(dāng)打印沒有加.default的serverEntry時万皿,可以發(fā)現(xiàn)如下:
{ __esModule: true,
default:
{ '$$typeof': Symbol(react.element),
type: [Function: App],
key: null,
ref: null,
props: {},
_owner: null,
_store: {} } }
default里才是需要的內(nèi)容摧找,所以需要找到default里。
(4) 用來區(qū)分靜態(tài)內(nèi)容還是服務(wù)端代碼牢硅。
(3) 在瀏覽器端最終產(chǎn)生的是DOM元素蹬耘,而在服務(wù)器端,最終產(chǎn)生的是字符串减余,因?yàn)榉祷亟o瀏覽器的是HTML字符串综苔,所以服務(wù)器渲染不需要指定容器元素,只有一個返回字符串函數(shù)renderToString位岔。
-
(4) 為了和瀏覽器端一致如筛,把返回的字符串嵌入到id="root"的與元素里,即用appString代替index.js中id="root"里的'<app></app>'抒抬。
服務(wù)端渲染后的頁面代碼杨刨,如下圖:
四、webpack-dev-server
Use webpack with a development server that provides live reloading. This should be used for development only.
根據(jù)官方的說明擦剑,可以知道妖胀,webpack-dev-server是一個用在開發(fā)環(huán)境中的一個服務(wù),可以實(shí)時更新惠勒。
1赚抡、安裝
安裝webpack-dev-server和cross-env包
npm install webpack-dev-server --save-dev
npm i cross-env --save-dev
2、使用
首先纠屋,在package.json中的scripts中增加一個"dev:client",如下:
"dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js",
因?yàn)閣ebpack-dev-server用于開發(fā)環(huán)境涂臣,所以在webpack配置文件中需要加以區(qū)分是開發(fā)環(huán)境還是生產(chǎn)環(huán)境,在webpack.config.client,js
添加如下配置:
將之前的module.exports={}
的寫法改成const config={};
然后給一個if條件判斷if(diDev) {}
巾遭;最后再module.exports = config;
const isDev = process.env.NPDE_ENV === 'development'; // (1)
const config = {
entry: {
app: path.join(__dirname, '../client/app.js'),
},
output: {
filename: '[name].[hash:5].js',
path: path.join(__dirname, '../dist'),
publicPath: '/public',
},
module: {
rules: [
{
test: /.jsx$/,
loader: 'babel-loader',
},
{
test: /.js$/,
loader: 'babel-loader',
exclude: [
path.join(__dirname, '../node_modules')
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '../client/template.html')
})
]
}
// 只有在開發(fā)中才使用的
if (isDev) {
config.entry = {
app:[
'react-hot-loader/patch',
path.join(__dirname, '../client/app.js')
]
}
config.devServer = { // (2)
host: '0.0.0.0', // (3)
port: '8088',
contentBase: path.join(__dirname, '../dist'),// (4)
hot: true, // (5)
overlay: { // (6)
errors: true
},
publicPath: '/public/', // (7)
historyApiFallback: { // (8)
index: '/public/index.html'
},
}
}
module.exports = config;
- (1) 定義一個開發(fā)環(huán)境給變量isDev
- (2) 設(shè)置一個devServer
- (3) 如果想外部其他人也能訪問肉康,host就設(shè)為0.0.0.0,默認(rèn)為localhost
- (4) 告訴服務(wù)器從哪里提供內(nèi)容灼舍。只有在你想要提供靜態(tài)文件時才需要。因?yàn)殪o態(tài)文件是放在output中的path中配置涨薪,所以這里和output中的path路徑一致就行骑素。
- (5) 啟用 webpack 的模塊熱替換(hot-module-replacement)特性
- (6) 如果webpack在編譯過程中出錯,則在網(wǎng)頁顯示信息
- (7) 此路徑下的打包文件可在瀏覽器中訪問刚夺,確保 publicPath 總是以斜杠(/)開頭和結(jié)尾献丑。
假設(shè)服務(wù)器運(yùn)行在 http://localhost:8080 并且 output.filename 被設(shè)置為 bundle.js末捣。默認(rèn) publicPath 是 "/public/",所以你的包(bundle)可以通過 http://localhost:8080/public/bundle.js 訪問创橄。 - (8) 任意的 404 響應(yīng)被替代為設(shè)置的index
注意:當(dāng)啟動webpack-dev-server時箩做,最好是刪除之前已經(jīng)打包的文件,如dist文件夾妥畏,因?yàn)楫?dāng)啟動devServer時候邦邦,默認(rèn)在硬盤中檢測打包項(xiàng)目目錄,如果有醉蚁,則直接訪問這個dist文件下js文件的燃辖,但這個dist下的js文件和html引入的js文件名不一致,所以會造成404錯誤网棍。
五黔龟、hot-module-replacement
可以在開發(fā)環(huán)境中,修改代碼后滥玷,在頁面中無刷新的看到代碼變化后的效果氏身,極大的提高了開發(fā)效率。
1惑畴、安裝
安裝react-hot-loader
插件蛋欣,它提供了React的hot-module-replacement的功能的工具。
npm install react-hot-loader --save-dev
2桨菜、配置
配置.babelrc文件:
"plugins": [
"react-hot-loader/babel"
]
修改webpack.config.client.js文件豁状,添加HotModuleReplacementPlugin:
const webpack = require('webpack'); // 引入webpack
if (isDev) {
config.entry = {
app:[
'react-hot-loader/patch',
path.join(__dirname, '../client/app.js')
]
}
// 在開發(fā)環(huán)境中加入一個HotModuleReplacementPlugin
config.plugins.push(new webpack.HotModuleReplacementPlugin())
}
修改app.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
import { AppContainer } from 'react-hot-loader';
const render = Component => {
ReactDOM.hydrate(
<AppContainer>
<Component />
</AppContainer>,
document.getElementById('root')
)
}
render(App);
if (module.hot) {
module.hot.accept('./App.jsx', () => { // (1)
const NextApp = require('./App.jsx').default;
render(NextApp)
})
}
- (1) accept方法給定依賴模塊的更新,并觸發(fā)一個 回調(diào)函數(shù) 來對這些更新做出響應(yīng)倒得。
六泻红、規(guī)范代碼
常用的規(guī)范代碼主要從代碼規(guī)范和代碼書寫格式等方面進(jìn)行規(guī)范,如eslint規(guī)范代碼風(fēng)格霞掺,editorconfig規(guī)范代碼文本格式等谊路。
1、為什么要規(guī)范代碼
- (1) 規(guī)范代碼有利于團(tuán)隊協(xié)作
- (2) 純手工規(guī)范費(fèi)時費(fèi)力菩彬,而且不能保證準(zhǔn)確性
- (3) 能配合編輯器(如vscode)自動提醒錯誤缠劝,提高開發(fā)效率
2、Eslint
安裝:
npm install eslint --save-dev
npm install babel-eslint --save-dev
npm install eslint-config --save-dev
npm install eslint-config-airbnb --save-dev
npm install eslint-config-standard --save-dev
npm install eslint-loader --save-dev
npm install eslint-plugin-import --save-dev
npm install eslint-plugin-jsx-a11y --save-dev
npm install eslint-plugin-node --save-dev
npm install eslint-plugin-promise --save-dev
npm install eslint-plugin-react --save-dev
npm install eslint-plugin-standard --save-dev
配置:
在項(xiàng)目根目錄創(chuàng)建一個.eslintrc文件骗灶,然后添加配置項(xiàng):
{
"extends": "standard"
}
直接繼承標(biāo)準(zhǔn)的eslint標(biāo)準(zhǔn)就行
另外惨恭,在client文件目錄下再建一個.eslintrc文件,因?yàn)閏lient文件中主要代碼是用jsx語法來書寫的耙旦,很多寫法和nodejs端是不一樣的脱羡,需要單獨(dú)配置,如下:
{
"parser": "babel-eslint",
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOption": {
"ecmaVersion": 6,
"sourceType": "module" // (1)
},
"extends":"airbnb",
"rules": {
"semi": [0]
}
}
主要繼承了美國Airbnb公司的React eslint規(guī)則。
- (1) 定義文件是 ECMAScript 模塊
如果希望代碼在每次編譯之前使用這些eslint規(guī)則去檢查一遍锉罐,可以在webpack.client.config.js中設(shè)置:
在module中增加一條rules:
{
enforce: 'pre', // (1)
test: /.(js|jsx)$/,
loader: 'exlint-loader',
exclude: [
path.join(__dirname, '../node_modules')
]
}
- (1) 在代碼編譯之前帆竹,執(zhí)行eslint-loader,如果出現(xiàn)錯誤脓规,停止編譯栽连,報出錯誤。
3侨舆、editorconfig
在根目錄創(chuàng)建.editorconfig文件
注意:如果是在vscode編輯器中使用.editorconfig秒紧,需要安裝一個插件 EditorConfig for VS Code,才能生效态罪。
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
insert_final_newline = true
完整代碼:請移步github