Skip to content

Commit

Permalink
feat: basic support for webpack 5
Browse files Browse the repository at this point in the history
  • Loading branch information
Shao Sicong authored and Shao Sicong committed Nov 11, 2020
1 parent ce7fde0 commit d2ad7ab
Show file tree
Hide file tree
Showing 27 changed files with 724 additions and 1,870 deletions.
18 changes: 1 addition & 17 deletions examples/vue-cssextract/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ module.exports = {
use: BabelMultiTargetPlugin.loader(),
}, {
test: /\.vue$/,
use: [
BabelMultiTargetPlugin.loader('vue-loader'),
],
use: BabelMultiTargetPlugin.loader('vue-loader'),
},
{
test: /\.css$/,
Expand All @@ -31,18 +29,4 @@ module.exports = {
],
}],
},

node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
// eslint-disable-next-line camelcase
child_process: 'empty',
},
}
14 changes: 0 additions & 14 deletions examples/vue-dynamic-import/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,4 @@ module.exports = {
},
],
},

node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
// eslint-disable-next-line camelcase
child_process: 'empty',
},
}
28 changes: 16 additions & 12 deletions src/babel-target.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { BabelLoaderTransformOptions, BabelPresetOptions } from 'babel-loader'
import * as webpack from 'webpack'
import Chunk = webpack.compilation.Chunk
import ChunkGroup = webpack.compilation.ChunkGroup
import Entrypoint = webpack.compilation.Entrypoint
import Module = webpack.compilation.Module
import { Chunk, ChunkGroup, ChunkGraph, Entrypoint, Module } from 'webpack'

import { BabelLoaderCacheDirectoryOption } from './babel.multi.target.options'
import { BabelTargetOptions } from './babel.target.options'
Expand Down Expand Up @@ -33,8 +29,9 @@ export type BabelTargetInfo = { [TOption in keyof BabelTargetOptions]: BabelTarg
// so, need to do this instead
const SIG = {
module: [
'disconnect',
'unseal',
// These are removed in webpack@5
// 'disconnect',
// 'unseal',
'isEntryModule',
'isInChunk',
],
Expand All @@ -51,7 +48,8 @@ const SIG = {
'hasEntryModule',
'addModule',
'removeModule',
'setModules',
// This is removed in webpack@5
// 'setModules',
'getNumberOfModules',
'addGroup',
'isInGroup',
Expand Down Expand Up @@ -137,7 +135,7 @@ export class BabelTarget implements BabelTargetInfo {
}

public static getTargetFromModule(module: Module): BabelTarget {
if (module.options && module.options.babelTarget) {
if (module.options?.babelTarget) {
return module.options.babelTarget
}

Expand All @@ -146,7 +144,7 @@ export class BabelTarget implements BabelTargetInfo {
}

for (const reason of module.reasons) {
if (reason.dependency && reason.dependency.babelTarget) {
if (reason.dependency?.babelTarget) {
return reason.dependency.babelTarget
}
if (reason.module) {
Expand All @@ -162,10 +160,16 @@ export class BabelTarget implements BabelTargetInfo {
}

public static getTargetFromEntrypoint(entrypoint: Entrypoint): BabelTarget {
if (!entrypoint.runtimeChunk.hasEntryModule()) {
if (!entrypoint.getRuntimeChunk().hasEntryModule()) {
return undefined
}
return BabelTarget.getTargetFromModule(entrypoint.runtimeChunk.entryModule)
const arr = Array.from(
ChunkGraph.getChunkGraphForChunk(
entrypoint.getRuntimeChunk(),
'Chunk.entryModule',
'DEP_WEBPACK_CHUNK_ENTRY_MODULE',
).getChunkEntryModulesIterable(entrypoint.getRuntimeChunk()))
return BabelTarget.getTargetFromModule(arr[0])
}

// eslint-disable-next-line
Expand Down
42 changes: 18 additions & 24 deletions src/babel.multi.target.html.updater.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { AlterAssetTagsData, HtmlTag, HtmlWebpackPlugin } from 'html-webpack-plugin'
import { compilation, Compiler, Plugin } from 'webpack'
import { getHooks, HtmlTagObject } from 'html-webpack-plugin'
import { Chunk, ChunkGroup, Compilation, Compiler, Plugin } from 'webpack'

import { BabelTarget } from './babel-target'
import { getAlterAssetTags, getHeadTags, getBodyTags } from './html-webpack-plugin.polyfill'
import { PLUGIN_NAME } from './plugin.name'
import { TargetedChunkMap } from './targeted.chunk'
import Chunk = compilation.Chunk
import ChunkGroup = compilation.ChunkGroup
import Compilation = compilation.Compilation

// Works with HtmlWebpackPlugin to make sure the targeted assets are referenced correctly
// Tags for assets whose target has `esModule` set are updated with the `"type"="module"` attribute
Expand All @@ -20,15 +16,15 @@ export class BabelMultiTargetHtmlUpdater implements Plugin {

constructor(private targets: BabelTarget[]) {}

public updateScriptTags(chunkMap: TargetedChunkMap, tags: HtmlTag[]): void {
public updateScriptTags(chunkMap: TargetedChunkMap, tags: HtmlTagObject[]): void {

tags
.forEach((tag: HtmlTag) => {
.forEach((tag: HtmlTagObject) => {
if (tag.tagName !== 'script') {
return
}

const targetedChunks = chunkMap.get(tag.attributes.src)
const targetedChunks = chunkMap.get(tag.attributes.src as string)
// chunks that are added outside of an entry point (e.g. by HtmlWebpackIncludeAssetsPlugin) will not be targeted
if (!targetedChunks) {
return
Expand Down Expand Up @@ -83,7 +79,7 @@ export class BabelMultiTargetHtmlUpdater implements Plugin {
public apply(compiler: Compiler): void {

compiler.hooks.afterPlugins.tap(PLUGIN_NAME, () => {
const htmlWebpackPlugin: HtmlWebpackPlugin = compiler.options.plugins
const htmlWebpackPlugin = compiler.options.plugins
// instanceof can act wonky since we don't actually keep our own dependency on html-webpack-plugin
// should we?
.find(plugin => plugin.constructor.name === 'HtmlWebpackPlugin') as any
Expand All @@ -94,18 +90,18 @@ export class BabelMultiTargetHtmlUpdater implements Plugin {

// not sure if this is a problem since webpack will wait for dependencies to load, but sorting
// by auto/dependency will result in a cyclic dependency error for lazy-loaded routes
htmlWebpackPlugin.options.chunksSortMode = 'none' as any
htmlWebpackPlugin.userOptions.chunksSortMode = 'none' as any

if ((htmlWebpackPlugin.options.chunks as any) !== 'all' &&
htmlWebpackPlugin.options.chunks &&
htmlWebpackPlugin.options.chunks.length
if ((htmlWebpackPlugin.userOptions.chunks as any) !== 'all' &&
htmlWebpackPlugin.userOptions.chunks &&
htmlWebpackPlugin.userOptions.chunks.length
) {
htmlWebpackPlugin.options.chunks = this.mapChunkNames(htmlWebpackPlugin.options.chunks as string[])
htmlWebpackPlugin.userOptions.chunks = this.mapChunkNames(htmlWebpackPlugin.userOptions.chunks as string[])
}

if (htmlWebpackPlugin.options.excludeChunks &&
htmlWebpackPlugin.options.excludeChunks.length) {
htmlWebpackPlugin.options.excludeChunks = this.mapChunkNames(htmlWebpackPlugin.options.excludeChunks)
if (htmlWebpackPlugin.userOptions.excludeChunks &&
htmlWebpackPlugin.userOptions.excludeChunks.length) {
htmlWebpackPlugin.userOptions.excludeChunks = this.mapChunkNames(htmlWebpackPlugin.userOptions.excludeChunks)
}

compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation: Compilation) => {
Expand All @@ -114,10 +110,8 @@ export class BabelMultiTargetHtmlUpdater implements Plugin {
return
}

const hook = getAlterAssetTags(compilation)

hook.tapPromise(`${PLUGIN_NAME} update asset tags`,
async (htmlPluginData: AlterAssetTagsData) => {
getHooks(compilation).alterAssetTagGroups.tapPromise(`${PLUGIN_NAME} update asset tags`,
async (htmlPluginData) => {
const chunkMap: TargetedChunkMap = compilation.chunkGroups.reduce((result: TargetedChunkMap, chunkGroup: ChunkGroup) => {
chunkGroup.chunks.forEach((chunk: Chunk) => {
chunk.files.forEach((file: string) => {
Expand All @@ -126,8 +120,8 @@ export class BabelMultiTargetHtmlUpdater implements Plugin {
})
return result
}, new TargetedChunkMap(compiler.options.output.publicPath))
this.updateScriptTags(chunkMap, getHeadTags(htmlPluginData))
this.updateScriptTags(chunkMap, getBodyTags(htmlPluginData))
this.updateScriptTags(chunkMap, htmlPluginData.headTags)
this.updateScriptTags(chunkMap, htmlPluginData.bodyTags)
return htmlPluginData
})

Expand Down
3 changes: 2 additions & 1 deletion src/babel.multi.target.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ export class BabelMultiTargetPlugin implements Plugin {
// magic starts here!
new BabelTargetEntryOptionPlugin(this.targets).apply(compiler)
new TargetingPlugin(this.targets, this.options.exclude, this.options.doNotTarget, compiler.options.externals).apply(compiler)
new NormalizeCssChunksPlugin(this.targets).apply(compiler)
// TODO
// new NormalizeCssChunksPlugin(this.targets).apply(compiler)
new BabelMultiTargetHtmlUpdater(this.targets).apply(compiler)
if (this.options.safari10NoModuleFix) {
new SafariNoModuleFixPlugin(this.options.safari10NoModuleFix).apply(compiler)
Expand Down
38 changes: 32 additions & 6 deletions src/babel.target.entry.dependency.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
import { BabelTarget } from './babel-target'
import { DEV_SERVER_CLIENT } from './constants'

import Dependency = require('webpack/lib/Dependency')
import ModuleDependency = require('webpack/lib/dependencies/ModuleDependency')

export interface EntryLoc {
interface EntryLoc {
name: string
index?: number
}

export interface BabelTargetEntryDependency extends Dependency {
babelTarget: BabelTarget
loc: EntryLoc
name: string
// TODO what's makeSerializable?
export class BabelTargetEntryDependency extends ModuleDependency {

public name: string
public loc: EntryLoc

public get type(): string {
return 'babel target entry'
}

public getResourceIdentifier(): string {
return `module${this.request}!${this.babelTarget.key}`
}

public get category(): string {
return 'esm'
}

constructor(public babelTarget: BabelTarget, request: string, public originalName: string, loc?: EntryLoc) {
super(`${request.startsWith(DEV_SERVER_CLIENT) ? request : babelTarget.getTargetedRequest(request)}`)

this.name = babelTarget.getTargetedAssetName(originalName)
if (!loc) {
loc = { name: `${this.request}:${babelTarget.key}` }
} else {
loc.name += `:${babelTarget.key}`
}
this.loc = loc
}
}
58 changes: 43 additions & 15 deletions src/babel.target.entry.option.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Compiler, Plugin } from 'webpack'

import { BabelTarget } from './babel-target'
import { BabelTargetMultiEntryPlugin } from './babel.target.multi.entry.plugin'
import { BabelTargetSingleEntryPlugin } from './babel.target.single.entry.plugin'
import { BabelTargetEntryPlugin } from './babel.target.entry.plugin'

// takes over processing of webpack's entry options so that it generates one entry per entry and target
// basically the same as webpack's built-in EntryOptionPlugin, just using the babel targeting stuff instead
Expand All @@ -15,27 +14,56 @@ export class BabelTargetEntryOptionPlugin implements Plugin {
constructor(private targets: BabelTarget[]) {
}

private itemToPlugin(context: string, item: string | string[], name: string): Plugin {
if (Array.isArray(item)) {
return new BabelTargetMultiEntryPlugin(this.targets, context, name, item)
// private itemToPlugin(context: string, item: string | string[], name: string): Plugin {
// if (Array.isArray(item)) {
// return new BabelTargetMultiEntryPlugin(this.targets, context, name, item)
// }
// if (this.targets.find(target => !!(target.additionalModules && target.additionalModules.length))) {
// return new BabelTargetMultiEntryPlugin(this.targets, context, name, [item])
// }
// return new BabelTargetSingleEntryPlugin(this.targets, context, name, item)
// }

private static entryDescriptionToOptions(compiler: Compiler, name: string, desc: any): any {
const options = {
name,
filename: desc.filename,
runtime: desc.runtime,
dependOn: desc.dependOn,
chunkLoading: desc.chunkLoading,
wasmLoading: desc.wasmLoading,
library: desc.library,
}
// TODO what does those plugins do?
if (desc.chunkLoading) {
// const EnableChunkLoadingPlugin = require("./javascript/EnableChunkLoadingPlugin")
// EnableChunkLoadingPlugin.checkEnabled(compiler, desc.chunkLoading)
}
if (this.targets.find(target => !!(target.additionalModules && target.additionalModules.length))) {
return new BabelTargetMultiEntryPlugin(this.targets, context, name, [item])
if (desc.wasmLoading) {
// const EnableWasmLoadingPlugin = require("./wasm/EnableWasmLoadingPlugin")
// EnableWasmLoadingPlugin.checkEnabled(compiler, desc.wasmLoading)
}
return new BabelTargetSingleEntryPlugin(this.targets, context, name, item)
if (desc.library) {
// const EnableLibraryPlugin = require("./library/EnableLibraryPlugin")
// EnableLibraryPlugin.checkEnabled(compiler, desc.library.type)
}
return options
}

public apply(compiler: Compiler): void {
compiler.hooks.entryOption.tap('EntryOptionPlugin', (context: string, entry: any) => {
if (typeof entry === 'string' || Array.isArray(entry)) {
this.itemToPlugin(context, entry, 'main').apply(compiler)
} else if (typeof entry === 'object') {
for (const name of Object.keys(entry)) {
this.itemToPlugin(context, entry[name], name).apply(compiler)
}
} else if (typeof entry === 'function') {
if (typeof entry === 'function') {
// TODO figure out why
throw new Error('not supported')
// new DynamicEntryPlugin(context, entry).apply(compiler)
} else{
for (const name of Object.keys(entry)) {
const desc = entry[name]
const options = BabelTargetEntryOptionPlugin.entryDescriptionToOptions(compiler, name, desc)
for (const entry of desc.import) {
new BabelTargetEntryPlugin(this.targets, context, entry, options).apply(compiler)
}
}
}
return true
})
Expand Down
27 changes: 16 additions & 11 deletions src/babel.target.entry.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { compilation, Compiler, Plugin } from 'webpack'
import Compilation = compilation.Compilation
import Dependency = compilation.Dependency
import NormalModuleFactory = compilation.NormalModuleFactory
import { Compiler, EntryPlugin, Compilation, Dependency, NormalModuleFactory } from 'webpack'

import { BabelTarget } from './babel-target'
import { BabelTargetSingleEntryDependency } from './babel.target.single.entry.dependency'
import { BabelTargetEntryDependency } from './babel.target.entry.dependency'

export abstract class BabelTargetEntryPlugin implements Plugin {

protected constructor(protected targets: BabelTarget[], protected context: string, protected name: string) {
}
export class BabelTargetEntryPlugin implements EntryPlugin {
public constructor(protected targets: BabelTarget[], public context: string,
public entry: string, public options: EntryPlugin['options']) {}

public apply(compiler: Compiler): void {
compiler.hooks.compilation.tap(
this.constructor.name,
(compilation: Compilation, { normalModuleFactory }: { normalModuleFactory: NormalModuleFactory }) => {
(compilation.dependencyFactories as Map<any, any>).set(
BabelTargetSingleEntryDependency,
compilation.dependencyFactories.set(
BabelTargetEntryDependency,
normalModuleFactory,
)
},
)

compiler.hooks.make.tapPromise(
this.constructor.name,
async (compilation: Compilation) => {
await Promise.all(this.targets.map(async target => {
const dep = new BabelTargetEntryDependency(target, this.entry, (this.options as any).name)
return await this.addEntry(compilation, dep)
}))
},
)
}

protected async addEntry(compilation: Compilation, dep: BabelTargetEntryDependency): Promise<void>
Expand Down

0 comments on commit d2ad7ab

Please sign in to comment.