Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
最近由于用著html-webpack-plugin覺得很不爽,于是乎想自己動手寫一個插件。原以為像gulp插件一樣半天上手一天寫完,但令人郁悶的是完全找不到相關的文章。一進官方文檔卻是被嚇傻了。首先是進入how to write a plugin看了一頁簡單的介紹。然后教程會告訴你,你需要去了解compiler和compilation這兩個對象,才能更好地寫webpack的插件,然后作者給了github的鏈接給你,讓你去看源代碼,我暈。不過幸好最后給了一個plugins的API文檔,才讓我開發的過程中稍微有點頭緒。
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;
}
};
首先,定義一個函數func,用戶設置的options基本就在這里處理。
其次,需要設一個func.prototype.apply函數。這個函數是提供給webpack運行時調用的。webpack會在這里注入compiler對象。
輸出complier對象,你會看到這一長串的內容,初步一看,我看出了兩大類(有補充的可以告訴我)。一個webpack運行時的參數,例如_plugins,這些數組里的函數應該是webpack內置的函數,用于在compiltion,this-compilation和should-emit事件觸發時調用的。另一個是用戶寫在webpack.config.js里的參數。隱約覺得這里好多未來都可能會是webpack暴露給用戶的接口,使webpack的定制化功能更強大。
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。其它的等下一篇有機會再說。
對比起compiler還有compiler.plugin函數。這個相當于是插件可以進行處理的webpack的運行中的一些任務點,webpack就是完成一個又一個任務而完成整個打包構建過程的。如make是最開始的起點, complie就是編譯任務點,after-complie是編譯完成,emit是即將準備生成文件,after-emit是生成文件之后等等,前面幾個都是比較生動形象的任務點。
至于compilation,它繼承于compiler,所以能拿到一切compiler的內容(所以你也會看到webpack的options),而且也有plugin函數來接入任務點。在compiler.plugin('emit')任務點輸出compilation,會得到大致下面的對象數據,因為實在太長,我只保留了最重要的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部份重要是因為如果你想借助webpack幫你生成文件,你需要像官方教程how to write a plugin在assets上寫上對應的文件信息。
除此以外,compilation.getStats這個函數也相當重要,能得到生產文件以及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: '' // 具體文件內容}
],
filteredModules: 0,
children: }
這里的chunks數組里,是對應會生成的文件,以及md5之后的文件名和路徑,里面還有文件對應的chunkhash(每個文件不同,但如果你使用ExtractTextPlugin將css文件獨立出來的話,它會與require它的js入口文件共享相同的chunkhash),而assets.hash則是統一的hash,對每個文件都一樣。值得關注的是chunks里的每個文件,都有source這一項目,提供給開發者直接拿到源文件內容(主要是js,如果是css且使用ExtractTextPlugin,則請自行打印出來參考)。
接下來,會以最近我寫的一個插件html-res-webpack-plugin作為引子,來介紹基本的寫插件原理。插件的邏輯就寫在index.js里。
首先,將用戶輸入的參數在定好的函數中處理,HtmlResWebpackPlugin。
然后,新增apply函數,在里面寫好插件需要切入的webpack任務點。目前HtmlResWebpackPlugin插件只用到emit這個任務點,其它幾個僅作為演示。
第三步,調用addFileToWebpackAsset方法,寫compilation.assets,借助webpack生成html文件。
第四步,在開發模式下(isWatch=true),直接生成html,但在生產模式下(isWatch=true),插件會開始對靜態資源(js,css)進行md5或者內聯。
第五步,調用findAssets方法是為了通過compilation.getStats拿到的數據,去匹配對應的靜態資源,還有找到對應的哈希(是chunkhash還是hash)。
最六步,調用addAssets方法,對靜態資源分別做內聯或者md5文件處理。內聯資源的函數是inlineRes,你會看到我使用了compilation.assets[hashFile].source 及 compilation.assets[hashFile].children[1]._value。前者是針對于js的,后者是針對使用了ExtractTextPlugin的css資源。
最后一步,即是內聯和md5完成后,再更新一下compilation.assets中對應生成html的source內容,才能正確地生成內聯和md5后的內容。
有興趣可以試用一下html-res-webpack-plugin這個插件(為什么要寫一個新的html生成插件,我在readme里寫了,此處不贅述),看看有哪些用得不爽之處。目前只是第一版,還不適合用于生產環境。希望第二版的時候能適用于更多的場景,以及性能更好。到是,我也會寫第二篇插件開發文章,將本文還沒提到的地方一一補充完整。也歡迎大家在這里發貼,或者指出本人的謬誤之處。
018 眼看就要過去了,今年的你相較去年技術上有怎樣的收獲呢?
不論你是正在自學前端遇到了瓶頸,還是對某些技術熟練掌握但某些還未涉足,都希望這份清單能對你有所幫助。由于頭條不讓站外鏈接,可以自行復制來源鏈接或者文末查看更多
作者:AlienZHOU
來源:
https://github.com/alienzhou/frontend-tech-list
學習文章的知識往往是碎片化的。而前端涉及到的面很廣,這些知識如果不進行有效梳理,則無法相互串聯、形成體系。因此,我結合工作體會將抽象出了一些前端基礎能力,并將看過、寫過的一些不錯的文章進行整理,形成了一份(純)前端技術清單。
不論你是正在自學前端,還是對前端某些技術熟練掌握但某些還未涉足,我都希望這份清單能幫助你 review 一些前端的基礎能力。
0. 年度報告
1. 基礎拾遺
溫故而知新,不知則習之,是以牢固根基。
1.1. JavaScript
1.2. CSS
1.3. 瀏覽器
2. 工程化與工具
軟件規模的擴大帶來了工程化的需求,前端也不例外。隨著 NodeJS 的出現,前端工程師可以使用熟悉的 JS 快速開發所需的工具。工具鏈生態的繁榮也是前端圈繁榮的一個寫照。
2.1. webpack
2.2. Gulp
2.3. Linter
2.4. 靜態類型(Typescript/Flow)
2.5. Babel
2.6. CSS預處理與模塊化
3. 性能優化
性能優化其實就是在理解瀏覽器的基礎上“因地制宜”,因此可以配合1.3節“瀏覽器”部分進行理解。
強烈推薦把 Google Web 上性能優化 Tab 中的文章都通讀一遍,其基本涵蓋了現代瀏覽器中性能優化的所有點,非常系統。下面也摘錄了其中一些個人認為非常不錯的篇幅。
3.1. 加載性能
3.2. 運行時性能
3.3. 前端緩存
3.4. 性能調試與實踐
3.5. 性能指標
4. 安全
很多安全風險老生常談,但是往往到出現問題時,才會被重視或者意識到。
4.1. XSS
4.2. CSRF
4.3. CSP
4.4. HTTPS
4.5. 安全實錄
5. 自動化測試
自動化測試是軟件工程的重要部分之一,但卻極容易被忽視。
5.1. 單元測試
5.2. 端到端測試 (E2E)
5.3. 其他
6. 框架與類庫
如果說基礎知識是道,那框架與工具可能就是術;學習與理解它們,但千萬不要成為它們的奴隸。
6.1. React
6.2. Vue
6.3. Redux
6.4. RxJS
7. 新技術/方向
前端領域新技術、新方向層出不窮,這里匯總一些新技術方向;作為開發者需要多了解但是不要盲從
7.1. PWA
7.2. CSS Houdini
7.3. Web Components
7.4. 微前端(Micro Frontends)
7.5. HTTP/2
7.6. WebAssembly
8. 業務相關
在業務中往往還有一些與“業務無關”的場景需求 —— 不論是什么業務幾乎都會遇到;因此,在變與不變中,我們更需要去抽象出這些問題。
8.1. 數據打點上報
8.2. 前端監控
8.3. A/B測試
8.4. “服務端推”
8.5. 動效
9. 不歸類的好文
開卷有益。
程序開發過程中,我們始終要謹記的一點就是:程序是寫給人看的,不是寫給機器看的。任何項目開發,都必須要考慮到人員迭代,我們不能讓下一個接手你代碼的人,在看到你寫的代碼時會說出這樣的話,“這個代碼是人寫出來的嗎?可讀性太差了”。因此,我們必須遵循一定的規范,讓代碼的可讀性更強。
今天,我們就一起來看下前端開發過程所能涉及到的跟HTML有關的規范問題。
HTML5
在HTML文件中,推薦使用支持HTML5特性的文檔聲明,<!DOCTYPE html>。
首先是在文件的命名上,應當采用駝峰式命名,首字母小寫,后面每個單詞首字母大寫,而且對于具體的文件應當具有語義化,能夠給人一種直觀的感受這個文件的作用是什么。現在前端開發開發過程中都講求模塊化開發,甚至是組件化開發,在文件命名時更應該以模塊名或者組件名來命名。
例如在寫一個AngularJS應用時,由于會涉及到Controller,Service,Filter等概念,我們會分別建立一個文件,假如這個模塊的名字是庫存管理stockManage,我們可以這樣來命名文件。
stockManageCtrl
stockChangeService
stockChangeFilter
我們所說的語義化指的是使用具有語義化的標簽,在H5中添加了類似于header, nav, article, section, aside, footer等標簽,從單詞的意思上我們也很容易看出標簽的含義。
我們不推薦使用只有div標簽的頁面,例如
不推薦使用
而是應該使用以下這種帶有語義化的標簽。
推薦使用
img標簽是網頁用來顯示照片的標簽,在頁面所有標簽中占據的比例非常之高,但是在使用img標簽時也有下面需要注意的點。
給定width和height屬性
因為瀏覽器在加載圖片的過程中,需要先下載圖片,然后再解析圖片的高度和寬度,如果不給img元素設定高度和寬度,這樣在圖片加載過程中會不斷的計算,重排頁面的布局,在網絡不好的時候就會經常出現元素出現不規律移動的情況。因此給img元素設定width和height屬性是必要的。
alt屬性
img標簽的alt屬性表示的是在圖片無法顯示時,使用文字來代替顯示,它可以用在以下幾個場景中:
網路延遲太大
src屬性指定路徑出錯
瀏覽器禁用圖像
由于其有良好的信息提示效果,并且有助于網頁SEO效果,強烈建議在img標簽中使用alt屬性。
而且很重要的一點是img標簽的引入是需要呈現出與頁面相關的內容,其他情況應該使用CSS樣式實現。例如我們不推薦下面這種情況。
不推薦
而推薦使用下面這種情況
推薦使用
前端文件主要包括HTML頁面文件,CSS樣式文件和Javascript腳本文件。我們應該讓三者各司其職,在HTML中不應該出現CSS和JS表達式;在JS文件中,不應該出現大量的HTML和CSS代碼。在HTML文檔中應當盡量少的引入CSS和JS文件。為了保證文件的純凈,我們應當遵循下面的原則。
一個HTML文件應該只引入一個CSS文件
合理運用JS合并技術(Gulp, Webpack插件),保證引入JS文件不多于兩個
不使用行內腳本元素(<script>alert('Hello World')</script>)
不在標簽上使用style內聯樣式
不要使用style屬性
腳本加載在網頁加載過程中是一個很耗性能的過程,如果把JS文件放在head標簽里,它的加載會一直阻塞DOM的解析,造成頁面延遲。
因此現在講求的是腳本的異步加載過程,我們會使用到async關鍵字,考慮到瀏覽器的兼容性,我們推薦使用下面的方式加載腳本。
推薦方式
合理使用ID和錨點可以非常方便的實現當前頁面間的跳轉,現在越來越多的教程網頁由于是單頁面,經常會用到錨點跳轉。
對錨點知識還不了解的,可以看看我寫的這篇文章《神奇的html錨點,讓你的網頁在內部自由的跳轉》。
今天這篇文章主要總結了前端開發過程中的HTML規范問題,相信大家也或多或少遇到過,希望這篇文章能加深大家的認識。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。