UNPKG

6.93 kBJavaScriptView Raw
1const loaderUtils = require('loader-utils')
2const path = require('path')
3const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin')
4const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
5const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin')
6const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin')
7const LimitChunkCountPlugin = require('webpack/lib/optimize/LimitChunkCountPlugin')
8const normalize = require('./utils/normalize')
9const parseRequest = require('./utils/parse-request')
10const getMainCompilation = require('./utils/get-main-compilation')
11const toPosix = require('./utils/to-posix')
12const config = require('./config')
13const hash = require('hash-sum')
14const fixRelative = require('./utils/fix-relative')
15
16const defaultResultSource = '// removed by extractor'
17
18module.exports = function (content) {
19 this.cacheable()
20 const options = loaderUtils.getOptions(this) || {}
21
22 const mainCompilation = getMainCompilation(this._compilation)
23 const mpx = mainCompilation.__mpx__
24
25 const packageName = mpx.currentPackageRoot || 'main'
26 const pagesMap = mpx.pagesMap
27 const componentsMap = mpx.componentsMap[packageName]
28
29 const extract = mpx.extract
30 const extractedMap = mpx.extractedMap
31 const mode = mpx.mode
32 const seenFile = mpx.extractSeenFile
33 const typeExtMap = config[mode].typeExtMap
34
35 const rootName = mainCompilation._preparedEntrypoints[0].name
36 const rootRequest = mainCompilation._preparedEntrypoints[0].request
37 const rootModule = mainCompilation.entries.find((module) => {
38 return module.rawRequest === rootRequest
39 })
40 const rootResourcePath = parseRequest(rootModule.resource).resourcePath
41
42 const resourceRaw = this.resource
43 const issuerResourceRaw = options.issuerResource
44
45 let resultSource = defaultResultSource
46
47 const getFile = (resourceRaw, type) => {
48 const resourcePath = parseRequest(resourceRaw).resourcePath
49 const id = `${mode}:${packageName}:${type}:${resourcePath}`
50 if (!seenFile[id]) {
51 const resourcePath = parseRequest(resourceRaw).resourcePath
52 let filename = pagesMap[resourcePath] || componentsMap[resourcePath]
53 if (!filename && resourcePath === rootResourcePath) {
54 filename = rootName
55 }
56
57 if (filename) {
58 seenFile[id] = filename + typeExtMap[type]
59 } else {
60 const resourceName = path.parse(resourcePath).name
61 const outputPath = path.join(type, resourceName + hash(resourcePath) + typeExtMap[type])
62 seenFile[id] = mpx.getPackageInfo(resourceRaw, {
63 outputPath,
64 isStatic: true,
65 error: (err) => {
66 this.emitError(err)
67 }
68 }).outputPath
69 }
70 }
71 return seenFile[id]
72 }
73
74 const type = options.type
75 const fromImport = options.fromImport
76 let index = +options.index
77
78 let issuerFile
79 if (issuerResourceRaw) {
80 issuerFile = getFile(issuerResourceRaw, type)
81 }
82
83 const file = getFile(resourceRaw, type)
84 const filename = /(.*)\..*/.exec(file)[1]
85
86 let sideEffects = () => {
87 }
88
89 if (index === -1) {
90 // 需要返回路径或产生副作用
91 switch (type) {
92 case 'styles':
93 if (issuerFile) {
94 let relativePath = toPosix(path.relative(path.dirname(issuerFile), file))
95 relativePath = fixRelative(relativePath, mode)
96 if (fromImport) {
97 resultSource = `module.exports = ${JSON.stringify(relativePath)};`
98 } else {
99 sideEffects = (additionalAssets) => {
100 additionalAssets[issuerFile] = additionalAssets[issuerFile] || []
101 additionalAssets[issuerFile].prefix = additionalAssets[issuerFile].prefix || []
102 additionalAssets[issuerFile].prefix.push(`@import "${relativePath}";\n`)
103 }
104 }
105 }
106 break
107 case 'template':
108 resultSource = `module.exports = __webpack_public_path__ + ${JSON.stringify(file)};`
109 break
110 }
111 index = 0
112 }
113
114 const id = `${file}:${index}:${issuerFile}:${fromImport}`
115
116 let nativeCallback
117 // 由于webpack中moduleMap只在compilation维度有效,不同子编译之间可能会对相同的引用文件进行重复的无效抽取,建立全局extractedMap避免这种情况出现
118 if (extractedMap[id]) {
119 return extractedMap[id]
120 } else {
121 extractedMap[id] = resultSource
122 nativeCallback = this.async()
123 }
124
125 // 使用子编译器生成需要抽离的json,styles和template
126 const contentLoader = normalize.lib('content-loader')
127 const request = `!!${contentLoader}?${JSON.stringify(options)}!${this.resource}`
128
129 const childFilename = 'extractor-filename'
130 const outputOptions = {
131 filename: childFilename
132 }
133 const childCompiler = mainCompilation.createChildCompiler(request, outputOptions, [
134 new NodeTemplatePlugin(outputOptions),
135 new LibraryTemplatePlugin(null, 'commonjs2'),
136 new NodeTargetPlugin(),
137 new SingleEntryPlugin(this.context, request, filename),
138 new LimitChunkCountPlugin({ maxChunks: 1 })
139 ])
140
141 childCompiler.hooks.thisCompilation.tap('MpxWebpackPlugin ', (compilation) => {
142 compilation.hooks.normalModuleLoader.tap('MpxWebpackPlugin', (loaderContext) => {
143 // 传递编译结果,子编译器进入content-loader后直接输出
144 loaderContext.__mpx__ = {
145 content,
146 fileDependencies: this.getDependencies(),
147 contextDependencies: this.getContextDependencies()
148 }
149 })
150 })
151
152 let source
153
154 childCompiler.hooks.afterCompile.tapAsync('MpxWebpackPlugin', (compilation, callback) => {
155 source = compilation.assets[childFilename] && compilation.assets[childFilename].source()
156
157 // Remove all chunk assets
158 compilation.chunks.forEach((chunk) => {
159 chunk.files.forEach((file) => {
160 delete compilation.assets[file]
161 })
162 })
163
164 callback()
165 })
166
167 childCompiler.runAsChild((err, entries, compilation) => {
168 if (err) return nativeCallback(err)
169 if (compilation.errors.length > 0) {
170 mainCompilation.errors.push(...compilation.errors)
171 }
172
173 compilation.fileDependencies.forEach((dep) => {
174 this.addDependency(dep)
175 }, this)
176 compilation.contextDependencies.forEach((dep) => {
177 this.addContextDependency(dep)
178 }, this)
179
180 if (!source) {
181 return nativeCallback(new Error('Didn\'t get a result from child compiler'))
182 }
183
184 try {
185 let text = this.exec(source, request)
186 if (Array.isArray(text)) {
187 text = text.map((item) => {
188 return item[1]
189 }).join('\n')
190 }
191
192 extract(text, file, index, sideEffects)
193
194 // 在production模式下移除extract残留空模块
195 if (resultSource === defaultResultSource && this.minimize) {
196 this._module.needRemove = true
197 }
198 } catch (err) {
199 return nativeCallback(err)
200 }
201 if (resultSource) {
202 nativeCallback(null, resultSource)
203 } else {
204 nativeCallback()
205 }
206 })
207}