Skip to content

Commit

Permalink
feat: expose context for transform hook
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Aug 23, 2021
1 parent 811c206 commit aa92da9
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 44 deletions.
14 changes: 14 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Compiler } from 'webpack'
import { PluginContextMeta as RollupContextMeta } from 'rollup'

export interface UnpluginContextMeta extends Partial<RollupContextMeta> {
framework: 'rollup' | 'vite' | 'webpack'
webpack?: {
compiler: Compiler
}
}

export interface UnpluginContext {
error(message: string | Error): void
warn(message: string | Error): void
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './define'
export * from './types'
export * from './context'
28 changes: 14 additions & 14 deletions src/rollup/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { Plugin as RollupPlugin } from 'rollup'
import { UnpluginInstance, UnpluginFactory, UnpluginOptions } from '../types'
import { UnpluginInstance, UnpluginFactory, UnpluginOptions, RollupPlugin } from '../types'
import { UnpluginContextMeta } from '../context'

export function getRollupPlugin <UserOptions = {}> (
factory: UnpluginFactory<UserOptions>
): UnpluginInstance<UserOptions>['rollup'] {
return (userOptions?: UserOptions) => {
const rawPlugin = factory(userOptions)
const meta: UnpluginContextMeta = {
framework: 'rollup'
}
const rawPlugin = factory(userOptions, meta)
return toRollupPlugin(rawPlugin)
}
}

export function toRollupPlugin (rawPlugin: UnpluginOptions) {
const plugin: RollupPlugin = {
...rawPlugin
}

if (rawPlugin.transform && rawPlugin.transformInclude) {
plugin.transform = (code, id) => {
if (rawPlugin.transformInclude && !rawPlugin.transformInclude(id)) {
export function toRollupPlugin (plugin: UnpluginOptions): RollupPlugin {
if (plugin.transform && plugin.transformInclude) {
const _transform = plugin.transform
plugin.transform = function (code, id) {
if (plugin.transformInclude && !plugin.transformInclude(id)) {
return null
}
return rawPlugin.transform!(code, id)
return _transform.call(this, code, id)
}
}

if (rawPlugin.rollup) {
Object.assign(plugin, rawPlugin.rollup)
if (plugin.rollup) {
Object.assign(plugin, plugin.rollup)
}

return plugin
Expand Down
15 changes: 9 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Plugin as RollupPlugin } from 'rollup'
import type { Compiler as WebpackCompiler } from 'webpack'
import type { Compiler as WebpackCompiler, WebpackPluginInstance } from 'webpack'
import type { Plugin as VitePlugin } from 'vite'
import VirtualModulesPlugin from 'webpack-virtual-modules'
import { UnpluginContext, UnpluginContextMeta } from './context'

export {
RollupPlugin,
Expand All @@ -15,30 +16,32 @@ export interface UnpluginOptions {
name: string;
enforce?: 'post' | 'pre' | undefined;
transformInclude?: (id: string) => boolean;
transform?: (code: string, id: string) => Thenable<string | { code: string; map: any; } | null | undefined>;
load?: (id?:string) => Thenable<string | null | undefined>
transform?: (this: UnpluginContext, code: string, id: string) => Thenable<string | { code: string; map: any; } | null | undefined>;
load?: (this: UnpluginContext, id?:string) => Thenable<string | null | undefined>
resolveId?: (id?:string) => Thenable<string | null | undefined>

// framework specify extends
rollup?: Partial<RollupPlugin>
webpack?: (compiler: WebpackCompiler) => void
vite?: Partial<VitePlugin>
}

export interface ResolvedUnpluginOptions extends UnpluginOptions {
// injected internal objects
__vfs?: VirtualModulesPlugin
}

export type UnpluginFactory<UserOptions> = (options?: UserOptions) => UnpluginOptions
export type UnpluginFactory<UserOptions> = (options: UserOptions | undefined, meta: UnpluginContextMeta) => UnpluginOptions

export interface UnpluginInstance<UserOptions> {
rollup: (options?: UserOptions) => RollupPlugin;
webpack: (options?: UserOptions) => any;
webpack: (options?: UserOptions) => WebpackPluginInstance;
vite: (options?: UserOptions) => VitePlugin;
raw: UnpluginFactory<UserOptions>
}

declare module 'webpack' {
interface Compiler {
$unpluginContext: Record<string, UnpluginOptions>
$unpluginContext: Record<string, ResolvedUnpluginOptions>
}
}
6 changes: 5 additions & 1 deletion src/vite/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { UnpluginContextMeta } from '../context'
import { toRollupPlugin } from '../rollup'
import { UnpluginInstance, UnpluginFactory, VitePlugin } from '../types'

export function getVitePlugin <UserOptions = {}> (
factory: UnpluginFactory<UserOptions>
): UnpluginInstance<UserOptions>['vite'] {
return (userOptions?: UserOptions) => {
const rawPlugin = factory(userOptions)
const meta: UnpluginContextMeta = {
framework: 'vite'
}
const rawPlugin = factory(userOptions, meta)

const plugin = toRollupPlugin(rawPlugin) as VitePlugin

Expand Down
51 changes: 30 additions & 21 deletions src/webpack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,64 @@ import fs from 'fs'
import { join, resolve } from 'path'
import type { Resolver } from 'enhanced-resolve'
import VirtualModulesPlugin from 'webpack-virtual-modules'
import type { UnpluginInstance, UnpluginFactory, WebpackCompiler } from '../types'
import { UnpluginContextMeta } from '../context'
import type { UnpluginInstance, UnpluginFactory, WebpackCompiler, ResolvedUnpluginOptions } from '../types'

export function getWebpackPlugin<UserOptions = {}> (
factory: UnpluginFactory<UserOptions>
): UnpluginInstance<UserOptions>['webpack'] {
return (userOptions?: UserOptions) => {
return {
apply (compiler: WebpackCompiler) {
const rawPlugin = factory(userOptions)
const meta: UnpluginContextMeta = {
framework: 'webpack',
webpack: {
compiler
}
}

const rawPlugin = factory(userOptions, meta)
const plugin = Object.assign(rawPlugin, { __unpluginMeta: meta }) as ResolvedUnpluginOptions
const loaderPath = resolve(__dirname, 'webpack/loaders')

const context = compiler.$unpluginContext || {}
compiler.$unpluginContext = context
context[rawPlugin.name] = rawPlugin
const injected = compiler.$unpluginContext || {}
compiler.$unpluginContext = injected
injected[plugin.name] = plugin

compiler.hooks.thisCompilation.tap(rawPlugin.name, (compilation) => {
compilation.hooks.childCompiler.tap(rawPlugin.name, (childCompiler) => {
childCompiler.$unpluginContext = context
compiler.hooks.thisCompilation.tap(plugin.name, (compilation) => {
compilation.hooks.childCompiler.tap(plugin.name, (childCompiler) => {
childCompiler.$unpluginContext = injected
})
})

if (rawPlugin.transform) {
if (plugin.transform) {
compiler.options.module.rules.push({
include (id: string) {
if (rawPlugin.transformInclude) {
return rawPlugin.transformInclude(id)
if (plugin.transformInclude) {
return plugin.transformInclude(id)
} else {
return true
}
},
enforce: rawPlugin.enforce,
enforce: plugin.enforce,
use: [{
loader: join(loaderPath, 'transform.cjs'),
options: {
unpluginName: rawPlugin.name
unpluginName: plugin.name
}
}]
})
}

if (rawPlugin.resolveId) {
if (plugin.resolveId) {
const virtualModule = new VirtualModulesPlugin()
rawPlugin.__vfs = virtualModule
plugin.__vfs = virtualModule
compiler.options.plugins.push(virtualModule)

const resolver = {
apply (resolver: Resolver) {
const tap = (target: any) => async (request: any, resolveContext: any, callback: any) => {
const resolved = await rawPlugin.resolveId!(request.request)
const resolved = await plugin.resolveId!(request.request)
if (resolved != null) {
const newRequest = {
...request,
Expand Down Expand Up @@ -82,23 +91,23 @@ export function getWebpackPlugin<UserOptions = {}> (
}

// TODO: not working for virtual module
if (rawPlugin.load) {
if (plugin.load) {
compiler.options.module.rules.push({
include () {
return true
},
enforce: rawPlugin.enforce,
enforce: plugin.enforce,
use: [{
loader: join(loaderPath, 'load.cjs'),
options: {
unpluginName: rawPlugin.name
unpluginName: plugin.name
}
}]
})
}

if (rawPlugin.webpack) {
rawPlugin.webpack(compiler)
if (plugin.webpack) {
plugin.webpack(compiler)
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/webpack/loaders/load.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { LoaderContext } from 'webpack'
import { UnpluginContext } from '../../context'
import '../../types'

export default async function load (this: LoaderContext<any>, source: string) {
Expand All @@ -10,7 +11,11 @@ export default async function load (this: LoaderContext<any>, source: string) {
return callback(null, source)
}

const res = await plugin.load(this.resource)
const context: UnpluginContext = {
error: error => this.emitError(typeof error === 'string' ? new Error(error) : error),
warn: error => this.emitWarning(typeof error === 'string' ? new Error(error) : error)
}
const res = await plugin.load.call(context, this.resource)

if (res == null) {
callback(null, source)
Expand Down
7 changes: 6 additions & 1 deletion src/webpack/loaders/transform.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { LoaderContext } from 'webpack'
import { UnpluginContext } from '../../context'
import '../../types'

export default async function transform (this: LoaderContext<any>, source: string, map: any) {
Expand All @@ -10,7 +11,11 @@ export default async function transform (this: LoaderContext<any>, source: strin
return callback(null, source, map)
}

const res = await plugin.transform(source, this.resource)
const context: UnpluginContext = {
error: error => this.emitError(typeof error === 'string' ? new Error(error) : error),
warn: error => this.emitWarning(typeof error === 'string' ? new Error(error) : error)
}
const res = await plugin.transform.call(context, source, this.resource)

if (res == null) {
callback(null, source, map)
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"esModuleInterop": true,
"strict": true,
"declaration": true,
"resolveJsonModule": true,
"types": [
"node",
"jest"
Expand Down

0 comments on commit aa92da9

Please sign in to comment.