4 回答

TA貢獻(xiàn)1884條經(jīng)驗(yàn) 獲得超4個(gè)贊
其實(shí)有兩種方案:第一種是將 css 文件在 js entry 中添加依賴;第二種直接設(shè)置 css entry。
第一種方案
// index.jsimport 'normalize.css';
...
// webpack config{ entry: { index: './index.js'
},
...
}
// outputindex.jsindex.css
這種是 Webpack 官方推薦的方案,但是每次都要把 css 放到 js entry 中才可以 extract 出來。
第二種方案(直接設(shè)置 css entry)
默認(rèn) Webpack 設(shè)置 css entry 除了 extract 出 css 文件還會(huì)多產(chǎn)生一個(gè) js 文件,其實(shí)可以寫個(gè) Webpack 插件將其刪除就可以了。

TA貢獻(xiàn)2003條經(jīng)驗(yàn) 獲得超2個(gè)贊
how to write a plugin這個(gè)教程還是可以好好看看的,尤其是那個(gè)simple example,它會(huì)教你在compilation的emit事件或之前,將你需要生成的文件放到webpack的compilation.assets里,這樣就可以借助webpack的力量幫你生成文件,而不需要自己手動(dòng)去寫fs.writeFileSync。
主要就是這段代碼
compilation.assets['filelist.md'] = {
source: function() {
return filelist;
},
size: function() {
return filelist.length;
}
};
基本特性介紹
首先,定義一個(gè)函數(shù)func,用戶設(shè)置的options基本就在這里處理。
其次,需要設(shè)一個(gè)func.prototype.apply函數(shù)。這個(gè)函數(shù)是提供給webpack運(yùn)行時(shí)調(diào)用的。webpack會(huì)在這里注入compiler對(duì)象。
輸出complier對(duì)象,你會(huì)看到這一長(zhǎng)串的內(nèi)容,初步一看,我看出了兩大類(有補(bǔ)充的可以告訴我)。一個(gè)webpack運(yùn)行時(shí)的參數(shù),例如_plugins,這些數(shù)組里的函數(shù)應(yīng)該是webpack內(nèi)置的函數(shù),用于在compiltion,this-compilation和should-emit事件觸發(fā)時(shí)調(diào)用的。另一個(gè)是用戶寫在webpack.config.js里的參數(shù)。隱約覺得這里好多未來都可能會(huì)是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對(duì)象。我目前用到的,主要是compilation。其它的等下一篇有機(jī)會(huì)再說。
對(duì)比起compiler還有compiler.plugin函數(shù)。這個(gè)相當(dāng)于是插件可以進(jìn)行處理的webpack的運(yùn)行中的一些任務(wù)點(diǎn),webpack就是完成一個(gè)又一個(gè)任務(wù)而完成整個(gè)打包構(gòu)建過程的。如make是最開始的起點(diǎn), complie就是編譯任務(wù)點(diǎn),after-complie是編譯完成,emit是即將準(zhǔn)備生成文件,after-emit是生成文件之后等等,前面幾個(gè)都是比較生動(dòng)形象的任務(wù)點(diǎn)。
至于compilation,它繼承于compiler,所以能拿到一切compiler的內(nèi)容(所以你也會(huì)看到webpack的options),而且也有plugin函數(shù)來接入任務(wù)點(diǎn)。在compiler.plugin('emit')任務(wù)點(diǎn)輸出compilation,會(huì)得到大致下面的對(duì)象數(shù)據(jù),因?yàn)閷?shí)在太長(zhǎng),我只保留了最重要的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上寫上對(duì)應(yīng)的文件信息。
除此以外,compilation.getStats()這個(gè)函數(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?sets[]=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ù)組里,是對(duì)應(yīng)會(huì)生成的文件,以及md5之后的文件名和路徑,里面還有文件對(duì)應(yīng)的chunkhash(每個(gè)文件不同,但如果你使用ExtractTextPlugin將css文件獨(dú)立出來的話,它會(huì)與require它的js入口文件共享相同的chunkhash),而assets.hash則是統(tǒng)一的hash,對(duì)每個(gè)文件都一樣。值得關(guān)注的是chunks里的每個(gè)文件,都有source這一項(xiàng)目,提供給開發(fā)者直接拿到源文件內(nèi)容(主要是js,如果是css且使用ExtractTextPlugin,則請(qǐng)自行打印出來參考)。
例子
接下來,會(huì)以最近我寫的一個(gè)插件 html-res-webpack-plugin 作為引子,來介紹基本的寫插件原理。插件的邏輯就寫在index.js里。
首先,將用戶輸入的參數(shù)在定好的函數(shù)中處理,HtmlResWebpackPlugin。
然后,新增apply函數(shù),在里面寫好插件需要切入的webpack任務(wù)點(diǎn)。目前HtmlResWebpackPlugin插件只用到emit這個(gè)任務(wù)點(diǎn),其它幾個(gè)僅作為演示。
第三步,調(diào)用addFileToWebpackAsset方法,寫compilation.assets,借助webpack生成html文件。
第四步,在開發(fā)模式下(isWatch = true),直接生成html,但在生產(chǎn)模式下(isWatch = true),插件會(huì)開始對(duì)靜態(tài)資源(js,css)進(jìn)行md5或者內(nèi)聯(lián)。
第五步,調(diào)用findAssets方法是為了通過compilation.getStats()拿到的數(shù)據(jù),去匹配對(duì)應(yīng)的靜態(tài)資源,還有找到對(duì)應(yīng)的哈希(是chunkhash還是hash)。
最六步,調(diào)用addAssets方法,對(duì)靜態(tài)資源分別做內(nèi)聯(lián)或者md5文件處理。內(nèi)聯(lián)資源的函數(shù)是inlineRes,你會(huì)看到我使用了compilation.assets[hashFile].source() 及 compilation.assets[hashFile].children[1]._value。前者是針對(duì)于js的,后者是針對(duì)使用了ExtractTextPlugin的css資源。
最后一步,即是內(nèi)聯(lián)和md5完成后,再更新一下compilation.assets中對(duì)應(yīng)生成html的source內(nèi)容,才能正確地生成內(nèi)聯(lián)和md5后的內(nèi)容。
后記
有興趣可以試用一下 html-res-webpack-plugin 這個(gè)插件(為什么要寫一個(gè)新的html生成插件,我在readme里寫了,此處不贅述),看看有哪些用得不爽之處。目前只是第一版,還不適合用于生產(chǎn)環(huán)境。希望第二版的時(shí)候能適用于更多的場(chǎng)景,以及性能更好。到是,我也會(huì)寫第二篇插件開發(fā)文章,將本文還沒提到的地方一一補(bǔ)充完整。也歡迎大家在這里發(fā)貼,或者指出本人的謬誤之處。
- 4 回答
- 0 關(guān)注
- 599 瀏覽
添加回答
舉報(bào)