最近由于用著html-webpack-plugin覺得很不爽,于是乎想自己動手寫一個插件。原以為像gulp插件一樣半天上手一天寫完,但令人郁悶的是完全找不到相關(guān)的文章。一進(jìn)官方文檔卻是被嚇傻了。首先是進(jìn)入how to write a plugin看了一頁簡單的介紹。然后教程會告訴你,你需要去了解compiler和compilation這兩個對象,才能更好地寫webpack的插件,然后作者給了github的鏈接給你,讓你去看源代碼,我暈。不過幸好最后給了一個plugins的API文檔,才讓我開發(fā)的過程中稍微有點(diǎn)頭緒。
how to write a plugin這個教程還是可以好好看看的,尤其是那個simple example,它會教你在compilation的emit事件或之前,將你需要生成的文件放到webpack的compilation.assets里,這樣就可以借助webpack的力量幫你生成文件,而不需要自己手動去寫fs.writeFileSync。
主要就是這段代碼
compilation.assets['filelist.md']={
source: function {
return filelist;
},
size: function {
return filelist.length;
}
};
首先,定義一個函數(shù)func,用戶設(shè)置的options基本就在這里處理。
其次,需要設(shè)一個func.prototype.apply函數(shù)。這個函數(shù)是提供給webpack運(yùn)行時(shí)調(diào)用的。webpack會在這里注入compiler對象。
輸出complier對象,你會看到這一長串的內(nèi)容,初步一看,我看出了兩大類(有補(bǔ)充的可以告訴我)。一個webpack運(yùn)行時(shí)的參數(shù),例如_plugins,這些數(shù)組里的函數(shù)應(yīng)該是webpack內(nèi)置的函數(shù),用于在compiltion,this-compilation和should-emit事件觸發(fā)時(shí)調(diào)用的。另一個是用戶寫在webpack.config.js里的參數(shù)。隱約覺得這里好多未來都可能會是webpack暴露給用戶的接口,使webpack的定制化功能更強(qiáng)大。
Compiler {
_plugins:
{ compilation: [ [Function], [Function], [Function], [Function] ],
'this-compilation': [ [Function: bound ] ],
'should-emit': [ [Function] ] },
outputPath: '',
outputFileSystem: null,
inputFileSystem: null,
recordsInputPath: null,
recordsOutputPath: null,
records: {},
fileTimestamps: {},
contextTimestamps: {},
resolvers:
{ normal: Tapable { _plugins: {}, fileSystem: null },
loader: Tapable { _plugins: {}, fileSystem: null },
context: Tapable { _plugins: {}, fileSystem: null } },
parser:
Parser {
_plugins:
{ 'evaluate Literal': [Object],
'evaluate LogicalExpression': [Object],
'evaluate BinaryExpression': [Object],
'evaluate UnaryExpression': [Object],
'evaluate typeof undefined': [Object],
'evaluate Identifier': [Object],
'evaluate MemberExpression': [Object],
'evaluate CallExpression': [Object],
'evaluate CallExpression .replace': [Object],
'evaluate CallExpression .substr': [Object],
'evaluate CallExpression .substring': [Object],
'evaluate CallExpression .split': [Object],
'evaluate ConditionalExpression': [Object],
'evaluate ArrayExpression': [Object],
'expression Spinner': [Object],
'expression ScreenMod': [Object] },
options: undefined },
options:
{ entry:
{
'index': '/Users/mac/web/src/page/index/main.js' },
output:
{ publicPath: '/homework/features/model/',
path: '/Users/mac/web/dist',
filename: 'js/[name].js',
libraryTarget: 'var',
sourceMapFilename: '[file].map[query]',
hotUpdateChunkFilename: '[id].[hash].hot-update.js',
hotUpdateMainFilename: '[hash].hot-update.json',
crossOriginLoading: false,
hashFunction: 'md5',
hashDigest: 'hex',
hashDigestLength: 20,
sourcePrefix: '\t',
devtoolLineToLine: false },
externals: { react: 'React' },
module:
{ loaders: [Object],
unknownContextRequest: '.',
unknownContextRecursive: true,
unknownContextRegExp: /^\.\/.*$/,
unknownContextCritical: true,
exprContextRequest: '.',
exprContextRegExp: /^\.\/.*$/,
exprContextRecursive: true,
exprContextCritical: true,
wrappedContextRegExp: /.*/,
wrappedContextRecursive: true,
wrappedContextCritical: false },
resolve:
{ extensions: [Object],
alias: [Object],
fastUnsafe: ,
packageAlias: 'browser',
modulesDirectories: [Object],
packageMains: [Object] },
plugins:
[ [Object],
[Object],
[Object],
[Object],
NoErrorsPlugin {},
[Object],
[Object] ],
devServer: { port: 8081, contentBase: './dist' },
context: '/Users/mac/web/',
watch: true,
debug: false,
devtool: false,
cache: true,
target: 'web',
node:
{ console: false,
process: true,
global: true,
setImmediate: true,
__filename: 'mock',
__dirname: 'mock' },
resolveLoader:
{ fastUnsafe: ,
alias: {},
modulesDirectories: [Object],
packageMains: [Object],
extensions: [Object],
moduleTemplates: [Object] },
optimize: { occurenceOrderPreferEntry: true } },
context: '/Users/mac/web/' }
除此以外,compiler還有一些如run, watch-run的方法以及compilation, normal-module-factory對象。我目前用到的,主要是compilation。其它的等下一篇有機(jī)會再說。
對比起compiler還有compiler.plugin函數(shù)。這個相當(dāng)于是插件可以進(jìn)行處理的webpack的運(yùn)行中的一些任務(wù)點(diǎn),webpack就是完成一個又一個任務(wù)而完成整個打包構(gòu)建過程的。如make是最開始的起點(diǎn), complie就是編譯任務(wù)點(diǎn),after-complie是編譯完成,emit是即將準(zhǔn)備生成文件,after-emit是生成文件之后等等,前面幾個都是比較生動形象的任務(wù)點(diǎn)。
至于compilation,它繼承于compiler,所以能拿到一切compiler的內(nèi)容(所以你也會看到webpack的options),而且也有plugin函數(shù)來接入任務(wù)點(diǎn)。在compiler.plugin('emit')任務(wù)點(diǎn)輸出compilation,會得到大致下面的對象數(shù)據(jù),因?yàn)閷?shí)在太長,我只保留了最重要的assets部份:
assetsCompilation {
assets:
{ 'js/index/main.js':
CachedSource {
_source: [Object],
_cachedSource: undefined,
_cachedSize: undefined,
_cachedMaps: {} } },
errors: ,
warnings: ,
children: ,
dependencyFactories:
ArrayMap {
keys:
[ [Object],
[Function: MultiEntryDependency],
[Function: SingleEntryDependency],
[Function: LoaderDependency],
[Object],
[Function: ContextElementDependency],
values:
[ NullFactory {},
[Object],
NullFactory {} ] },
dependencyTemplates:
ArrayMap {
keys:
[ [Object],
[Object],
[Object] ],
values:
[ ConstDependencyTemplate {},
RequireIncludeDependencyTemplate {},
NullDependencyTemplate {},
RequireEnsureDependencyTemplate {},
ModuleDependencyTemplateAsRequireId {},
AMDRequireDependencyTemplate {},
ModuleDependencyTemplateAsRequireId {},
AMDRequireArrayDependencyTemplate {},
ContextDependencyTemplateAsRequireCall {},
AMDRequireDependencyTemplate {},
LocalModuleDependencyTemplate {},
ModuleDependencyTemplateAsId {},
ContextDependencyTemplateAsRequireCall {},
ModuleDependencyTemplateAsId {},
ContextDependencyTemplateAsId {},
RequireResolveHeaderDependencyTemplate {},
RequireHeaderDependencyTemplate {} ] },
fileTimestamps: {},
contextTimestamps: {},
name: undefined,
_currentPluginApply: undefined,
fullHash: 'f4030c2aeb811dd6c345ea11a92f4f57',
hash: 'f4030c2aeb811dd6c345',
fileDependencies: [ '/Users/mac/web/src/js/index/main.js' ],
contextDependencies: ,
missingDependencies: }
assets部份重要是因?yàn)槿绻阆虢柚鷚ebpack幫你生成文件,你需要像官方教程how to write a plugin在assets上寫上對應(yīng)的文件信息。
除此以外,compilation.getStats這個函數(shù)也相當(dāng)重要,能得到生產(chǎn)文件以及chunkhash的一些信息,如下:
assets{ errors: ,
warnings: ,
version: '1.12.9',
hash: '5a5c71cb2accb8970bc3',
publicPath: 'xxxxxxxxxx',
assetsByChunkName: { 'index/main': 'js/index/index-4c0c16.js' },
assets:
[ { name: 'js/index/index-4c0c16.js',
size: 453,
chunks: [Object],
chunkNames: [Object],
emitted: undefined } ],
chunks:
[ { id: 0,
rendered: true,
initial: true,
entry: true,
extraAsync: false,
size: 221,
names: [Object],
files: [Object],
hash: '4c0c16e8af4d497b90ad',
parents: ,
origins: [Object] } ],
modules:
[ { id: 0,
identifier: 'multi index/main',
name: 'multi index/main',
index: 0,
index2: 1,
size: 28,
cacheable: true,
built: true,
optional: false,
prefetched: false,
chunks: [Object],
assets: ,
issuer: null,
profile: undefined,
failed: false,
errors: 0,
warnings: 0,
reasons: },
{ id: 1,
identifier: '/Users/mac/web/node_modules/babel-loader/index.js?presets=es2015&presets=react!/Users/mac/web/src/js/main/index.js',
name: './src/js/index/main.js',
index: 1,
index2: 0,
size: 193,
cacheable: true,
built: true,
optional: false,
prefetched: false,
chunks: [Object],
assets: ,
issuer: 'multi index/main',
profile: undefined,
failed: false,
errors: 0,
warnings: 0,
reasons: [Object],
source: '' // 具體文件內(nèi)容}
],
filteredModules: 0,
children: }
這里的chunks數(shù)組里,是對應(yīng)會生成的文件,以及md5之后的文件名和路徑,里面還有文件對應(yīng)的chunkhash(每個文件不同,但如果你使用ExtractTextPlugin將css文件獨(dú)立出來的話,它會與require它的js入口文件共享相同的chunkhash),而assets.hash則是統(tǒng)一的hash,對每個文件都一樣。值得關(guān)注的是chunks里的每個文件,都有source這一項(xiàng)目,提供給開發(fā)者直接拿到源文件內(nèi)容(主要是js,如果是css且使用ExtractTextPlugin,則請自行打印出來參考)。
接下來,會以最近我寫的一個插件html-res-webpack-plugin作為引子,來介紹基本的寫插件原理。插件的邏輯就寫在index.js里。
首先,將用戶輸入的參數(shù)在定好的函數(shù)中處理,HtmlResWebpackPlugin。
然后,新增apply函數(shù),在里面寫好插件需要切入的webpack任務(wù)點(diǎn)。目前HtmlResWebpackPlugin插件只用到emit這個任務(wù)點(diǎn),其它幾個僅作為演示。
第三步,調(diào)用addFileToWebpackAsset方法,寫compilation.assets,借助webpack生成html文件。
第四步,在開發(fā)模式下(isWatch=true),直接生成html,但在生產(chǎn)模式下(isWatch=true),插件會開始對靜態(tài)資源(js,css)進(jìn)行md5或者內(nèi)聯(lián)。
第五步,調(diào)用findAssets方法是為了通過compilation.getStats拿到的數(shù)據(jù),去匹配對應(yīng)的靜態(tài)資源,還有找到對應(yīng)的哈希(是chunkhash還是hash)。
最六步,調(diào)用addAssets方法,對靜態(tài)資源分別做內(nèi)聯(lián)或者md5文件處理。內(nèi)聯(lián)資源的函數(shù)是inlineRes,你會看到我使用了compilation.assets[hashFile].source 及 compilation.assets[hashFile].children[1]._value。前者是針對于js的,后者是針對使用了ExtractTextPlugin的css資源。
最后一步,即是內(nèi)聯(lián)和md5完成后,再更新一下compilation.assets中對應(yīng)生成html的source內(nèi)容,才能正確地生成內(nèi)聯(lián)和md5后的內(nèi)容。
有興趣可以試用一下html-res-webpack-plugin這個插件(為什么要寫一個新的html生成插件,我在readme里寫了,此處不贅述),看看有哪些用得不爽之處。目前只是第一版,還不適合用于生產(chǎn)環(huán)境。希望第二版的時(shí)候能適用于更多的場景,以及性能更好。到是,我也會寫第二篇插件開發(fā)文章,將本文還沒提到的地方一一補(bǔ)充完整。也歡迎大家在這里發(fā)貼,或者指出本人的謬誤之處。
018 眼看就要過去了,今年的你相較去年技術(shù)上有怎樣的收獲呢?
不論你是正在自學(xué)前端遇到了瓶頸,還是對某些技術(shù)熟練掌握但某些還未涉足,都希望這份清單能對你有所幫助。由于頭條不讓站外鏈接,可以自行復(fù)制來源鏈接或者文末查看更多
作者:AlienZHOU
來源:
https://github.com/alienzhou/frontend-tech-list
學(xué)習(xí)文章的知識往往是碎片化的。而前端涉及到的面很廣,這些知識如果不進(jìn)行有效梳理,則無法相互串聯(lián)、形成體系。因此,我結(jié)合工作體會將抽象出了一些前端基礎(chǔ)能力,并將看過、寫過的一些不錯的文章進(jìn)行整理,形成了一份(純)前端技術(shù)清單。
不論你是正在自學(xué)前端,還是對前端某些技術(shù)熟練掌握但某些還未涉足,我都希望這份清單能幫助你 review 一些前端的基礎(chǔ)能力。
0. 年度報(bào)告
1. 基礎(chǔ)拾遺
溫故而知新,不知則習(xí)之,是以牢固根基。
1.1. JavaScript
1.2. CSS
1.3. 瀏覽器
2. 工程化與工具
軟件規(guī)模的擴(kuò)大帶來了工程化的需求,前端也不例外。隨著 NodeJS 的出現(xiàn),前端工程師可以使用熟悉的 JS 快速開發(fā)所需的工具。工具鏈生態(tài)的繁榮也是前端圈繁榮的一個寫照。
2.1. webpack
2.2. Gulp
2.3. Linter
2.4. 靜態(tài)類型(Typescript/Flow)
2.5. Babel
2.6. CSS預(yù)處理與模塊化
3. 性能優(yōu)化
性能優(yōu)化其實(shí)就是在理解瀏覽器的基礎(chǔ)上“因地制宜”,因此可以配合1.3節(jié)“瀏覽器”部分進(jìn)行理解。
強(qiáng)烈推薦把 Google Web 上性能優(yōu)化 Tab 中的文章都通讀一遍,其基本涵蓋了現(xiàn)代瀏覽器中性能優(yōu)化的所有點(diǎn),非常系統(tǒng)。下面也摘錄了其中一些個人認(rèn)為非常不錯的篇幅。
3.1. 加載性能
3.2. 運(yùn)行時(shí)性能
3.3. 前端緩存
3.4. 性能調(diào)試與實(shí)踐
3.5. 性能指標(biāo)
4. 安全
很多安全風(fēng)險(xiǎn)老生常談,但是往往到出現(xiàn)問題時(shí),才會被重視或者意識到。
4.1. XSS
4.2. CSRF
4.3. CSP
4.4. HTTPS
4.5. 安全實(shí)錄
5. 自動化測試
自動化測試是軟件工程的重要部分之一,但卻極容易被忽視。
5.1. 單元測試
5.2. 端到端測試 (E2E)
5.3. 其他
6. 框架與類庫
如果說基礎(chǔ)知識是道,那框架與工具可能就是術(shù);學(xué)習(xí)與理解它們,但千萬不要成為它們的奴隸。
6.1. React
6.2. Vue
6.3. Redux
6.4. RxJS
7. 新技術(shù)/方向
前端領(lǐng)域新技術(shù)、新方向?qū)映霾桓F,這里匯總一些新技術(shù)方向;作為開發(fā)者需要多了解但是不要盲從
7.1. PWA
7.2. CSS Houdini
7.3. Web Components
7.4. 微前端(Micro Frontends)
7.5. HTTP/2
7.6. WebAssembly
8. 業(yè)務(wù)相關(guān)
在業(yè)務(wù)中往往還有一些與“業(yè)務(wù)無關(guān)”的場景需求 —— 不論是什么業(yè)務(wù)幾乎都會遇到;因此,在變與不變中,我們更需要去抽象出這些問題。
8.1. 數(shù)據(jù)打點(diǎn)上報(bào)
8.2. 前端監(jiān)控
8.3. A/B測試
8.4. “服務(wù)端推”
8.5. 動效
9. 不歸類的好文
開卷有益。
程序開發(fā)過程中,我們始終要謹(jǐn)記的一點(diǎn)就是:程序是寫給人看的,不是寫給機(jī)器看的。任何項(xiàng)目開發(fā),都必須要考慮到人員迭代,我們不能讓下一個接手你代碼的人,在看到你寫的代碼時(shí)會說出這樣的話,“這個代碼是人寫出來的嗎?可讀性太差了”。因此,我們必須遵循一定的規(guī)范,讓代碼的可讀性更強(qiáng)。
今天,我們就一起來看下前端開發(fā)過程所能涉及到的跟HTML有關(guān)的規(guī)范問題。
HTML5
在HTML文件中,推薦使用支持HTML5特性的文檔聲明,<!DOCTYPE html>。
首先是在文件的命名上,應(yīng)當(dāng)采用駝峰式命名,首字母小寫,后面每個單詞首字母大寫,而且對于具體的文件應(yīng)當(dāng)具有語義化,能夠給人一種直觀的感受這個文件的作用是什么?,F(xiàn)在前端開發(fā)開發(fā)過程中都講求模塊化開發(fā),甚至是組件化開發(fā),在文件命名時(shí)更應(yīng)該以模塊名或者組件名來命名。
例如在寫一個AngularJS應(yīng)用時(shí),由于會涉及到Controller,Service,F(xiàn)ilter等概念,我們會分別建立一個文件,假如這個模塊的名字是庫存管理stockManage,我們可以這樣來命名文件。
stockManageCtrl
stockChangeService
stockChangeFilter
我們所說的語義化指的是使用具有語義化的標(biāo)簽,在H5中添加了類似于header, nav, article, section, aside, footer等標(biāo)簽,從單詞的意思上我們也很容易看出標(biāo)簽的含義。
我們不推薦使用只有div標(biāo)簽的頁面,例如
不推薦使用
而是應(yīng)該使用以下這種帶有語義化的標(biāo)簽。
推薦使用
img標(biāo)簽是網(wǎng)頁用來顯示照片的標(biāo)簽,在頁面所有標(biāo)簽中占據(jù)的比例非常之高,但是在使用img標(biāo)簽時(shí)也有下面需要注意的點(diǎn)。
給定width和height屬性
因?yàn)闉g覽器在加載圖片的過程中,需要先下載圖片,然后再解析圖片的高度和寬度,如果不給img元素設(shè)定高度和寬度,這樣在圖片加載過程中會不斷的計(jì)算,重排頁面的布局,在網(wǎng)絡(luò)不好的時(shí)候就會經(jīng)常出現(xiàn)元素出現(xiàn)不規(guī)律移動的情況。因此給img元素設(shè)定width和height屬性是必要的。
alt屬性
img標(biāo)簽的alt屬性表示的是在圖片無法顯示時(shí),使用文字來代替顯示,它可以用在以下幾個場景中:
網(wǎng)路延遲太大
src屬性指定路徑出錯
瀏覽器禁用圖像
由于其有良好的信息提示效果,并且有助于網(wǎng)頁SEO效果,強(qiáng)烈建議在img標(biāo)簽中使用alt屬性。
而且很重要的一點(diǎn)是img標(biāo)簽的引入是需要呈現(xiàn)出與頁面相關(guān)的內(nèi)容,其他情況應(yīng)該使用CSS樣式實(shí)現(xiàn)。例如我們不推薦下面這種情況。
不推薦
而推薦使用下面這種情況
推薦使用
前端文件主要包括HTML頁面文件,CSS樣式文件和Javascript腳本文件。我們應(yīng)該讓三者各司其職,在HTML中不應(yīng)該出現(xiàn)CSS和JS表達(dá)式;在JS文件中,不應(yīng)該出現(xiàn)大量的HTML和CSS代碼。在HTML文檔中應(yīng)當(dāng)盡量少的引入CSS和JS文件。為了保證文件的純凈,我們應(yīng)當(dāng)遵循下面的原則。
一個HTML文件應(yīng)該只引入一個CSS文件
合理運(yùn)用JS合并技術(shù)(Gulp, Webpack插件),保證引入JS文件不多于兩個
不使用行內(nèi)腳本元素(<script>alert('Hello World')</script>)
不在標(biāo)簽上使用style內(nèi)聯(lián)樣式
不要使用style屬性
腳本加載在網(wǎng)頁加載過程中是一個很耗性能的過程,如果把JS文件放在head標(biāo)簽里,它的加載會一直阻塞DOM的解析,造成頁面延遲。
因此現(xiàn)在講求的是腳本的異步加載過程,我們會使用到async關(guān)鍵字,考慮到瀏覽器的兼容性,我們推薦使用下面的方式加載腳本。
推薦方式
合理使用ID和錨點(diǎn)可以非常方便的實(shí)現(xiàn)當(dāng)前頁面間的跳轉(zhuǎn),現(xiàn)在越來越多的教程網(wǎng)頁由于是單頁面,經(jīng)常會用到錨點(diǎn)跳轉(zhuǎn)。
對錨點(diǎn)知識還不了解的,可以看看我寫的這篇文章《神奇的html錨點(diǎn),讓你的網(wǎng)頁在內(nèi)部自由的跳轉(zhuǎn)》。
今天這篇文章主要總結(jié)了前端開發(fā)過程中的HTML規(guī)范問題,相信大家也或多或少遇到過,希望這篇文章能加深大家的認(rèn)識。
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。