Skip to content

Commit

Permalink
feat: factory style, close #2
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jul 12, 2021
1 parent ca83e39 commit 8f4fc53
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 62 deletions.
30 changes: 14 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,23 @@
```ts
import { defineUnplugin } from 'unplugin'

const plugin = defineUnplugin({
name: 'my-first-unplugin',
setup(options: UserOptions) {
return {
// webpack's id filter is outside of loader logic,
// an additional hook is needed for better perf on webpack
transformInclude (id) {
return id.endsWith('.vue')
},
// just like rollup transform
transform (code) {
return code.replace(/<template>/, `<template><div>Injected</div>`)
},
// more hooks incoming
}
const plugin = defineUnplugin((options: UserOptions) => {
return {
name: 'my-first-unplugin',
// webpack's id filter is outside of loader logic,
// an additional hook is needed for better perf on webpack
transformInclude (id) {
return id.endsWith('.vue')
},
// just like rollup transform
transform (code) {
return code.replace(/<template>/, `<template><div>Injected</div>`)
},
// more hooks incoming
}
})

const rollupPlugin = plugin.rollup()
const rollupPlugin = plugin.rollup({ /* ...user options */ })
const webpackPlugin = plugin.webpack()
```

Expand Down
8 changes: 4 additions & 4 deletions src/define.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { getRollupPlugin } from './rollup'
import { UnpluginOptions, UnpluginInstance } from './types'
import { UnpluginInstance, UnpluginFactory } from './types'
import { getWebpackPlugin } from './webpack'

export function defineUnplugin<UserOptions = {}> (
options: UnpluginOptions<UserOptions>
factory: UnpluginFactory<UserOptions>
): UnpluginInstance<UserOptions> {
return {
get rollup () {
return getRollupPlugin(options)
return getRollupPlugin(factory)
},
get webpack () {
return getWebpackPlugin(options)
return getWebpackPlugin(factory)
}
}
}
23 changes: 14 additions & 9 deletions src/rollup.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { UnpluginInstance, UnpluginOptions } from './types'
import { Plugin as RollupPlugin } from 'rollup'
import { UnpluginInstance, UnpluginFactory } from './types'

export function getRollupPlugin <UserOptions = {}> (
options: UnpluginOptions<UserOptions>
factory: UnpluginFactory<UserOptions>
): UnpluginInstance<UserOptions>['rollup'] {
return (userOptions?: UserOptions) => {
const hooks = options.setup(userOptions)
const rawPlugin = factory(userOptions)

return {
name: options.name,
enforce: options.enforce,
transform (code, id) {
if (hooks.transformInclude && !hooks.transformInclude(id)) {
const plugin: RollupPlugin = {
...rawPlugin
}

if (rawPlugin.transform && rawPlugin.transformInclude) {
plugin.transform = (code, id) => {
if (rawPlugin.transformInclude && !rawPlugin.transformInclude(id)) {
return null
}
return hooks.transform?.(code, id) || null
return rawPlugin.transform!(code, id)
}
}

return plugin
}
}
10 changes: 5 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { Plugin as RollupPlugin } from 'rollup'
export type Thenable<T> = T | Promise<T>

export interface UnpluginHooks {
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>
resolveId?: (id?:string) => Thenable<string | null | undefined>
}

export interface UnpluginOptions<UserOptions> {
name: string;
enforce?: 'post' | 'pre' | undefined;
setup(options?: UserOptions): UnpluginHooks;
}
export type UnpluginFactory<UserOptions> = (options?: UserOptions) => UnpluginHooks

export interface UnpluginInstance<UserOptions> {
rollup: (options?: UserOptions) => RollupPlugin;
Expand Down
59 changes: 31 additions & 28 deletions src/webpack.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,37 @@
import fs from 'fs'
import { UnpluginInstance, UnpluginOptions } from './types'
import { UnpluginInstance, UnpluginFactory } from './types'
import { getLoaderPath } from './utils'

export function getWebpackPlugin<UserOptions = {}> (
options: UnpluginOptions<UserOptions>
factory: UnpluginFactory<UserOptions>
): UnpluginInstance<UserOptions>['webpack'] {
class UnpluginWebpackPlugin {
// eslint-disable-next-line no-useless-constructor
constructor (public userOptions?: UserOptions) {}
apply (compiler: any) {
const hooks = options.setup(this.userOptions)
const rawPlugin = factory(this.userOptions)

if (!compiler.$unplugin) {
compiler.$unplugin = {}
}
compiler.$unplugin[options.name] = hooks
compiler.$unplugin[rawPlugin.name] = rawPlugin

if (hooks.transform) {
const loaderPath = getLoaderPath(options.name)
if (rawPlugin.transform) {
const loaderPath = getLoaderPath(rawPlugin.name)

fs.writeFileSync(loaderPath, `
module.exports = async function(source, map) {
const callback = this.async()
const plugin = this._compiler.$unplugin['${options.name}']
const res = await plugin.transform(source, this.resource)
if (res == null) {
callback(null, source, map)
}
else if (typeof res !== 'string') {
callback(null, res.code, res.map)
}
else {
callback(null, res, map)
}
}
`, 'utf-8')
generateLoader(loaderPath, rawPlugin.name)

compiler.options.module.rules.push({
include (id: string) {
if (hooks.transformInclude) {
return hooks.transformInclude(id)
if (rawPlugin.transformInclude) {
return rawPlugin.transformInclude(id)
} else {
return true
}
},
enforce: options.enforce,
enforce: rawPlugin.enforce,
use: [{
ident: options.name,
ident: rawPlugin.name,
loader: loaderPath
}]
})
Expand All @@ -58,3 +41,23 @@ module.exports = async function(source, map) {

return UserOptions => new UnpluginWebpackPlugin(UserOptions)
}

function generateLoader (loaderPath: string, name: string) {
fs.writeFileSync(loaderPath, `
module.exports = async function(source, map) {
const callback = this.async()
const plugin = this._compiler.$unplugin['${name}']
const res = await plugin.transform(source, this.resource)
if (res == null) {
callback(null, source, map)
}
else if (typeof res !== 'string') {
callback(null, res.code, res.map)
}
else {
callback(null, res, map)
}
}`, 'utf-8')
}

0 comments on commit 8f4fc53

Please sign in to comment.