Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions crates/binding/src/js_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use napi::NapiRaw;
use napi_derive::napi;
use serde_json::Value;

use crate::js_plugin::PluginContext;
use crate::threadsafe_function::ThreadsafeFunction;

#[napi(object)]
Expand Down Expand Up @@ -81,19 +82,21 @@ pub struct JsHooks {
pub transform_include: Option<JsFunction>,
}

type ResolveIdFuncParams = (PluginContext, String, String, ResolveIdParams);

pub struct TsFnHooks {
pub build_start: Option<ThreadsafeFunction<(), ()>>,
pub build_end: Option<ThreadsafeFunction<(), ()>>,
pub write_bundle: Option<ThreadsafeFunction<(), ()>>,
pub generate_end: Option<ThreadsafeFunction<Value, ()>>,
pub load: Option<ThreadsafeFunction<String, Option<LoadResult>>>,
pub load_include: Option<ThreadsafeFunction<String, Option<bool>>>,
pub watch_changes: Option<ThreadsafeFunction<(String, WatchChangesParams), ()>>,
pub resolve_id:
Option<ThreadsafeFunction<(String, String, ResolveIdParams), Option<ResolveIdResult>>>,
pub _on_generate_file: Option<ThreadsafeFunction<WriteFile, ()>>,
pub transform: Option<ThreadsafeFunction<(String, String), Option<TransformResult>>>,
pub transform_include: Option<ThreadsafeFunction<String, Option<bool>>>,
pub build_start: Option<ThreadsafeFunction<PluginContext, ()>>,
pub build_end: Option<ThreadsafeFunction<PluginContext, ()>>,
pub write_bundle: Option<ThreadsafeFunction<PluginContext, ()>>,
pub generate_end: Option<ThreadsafeFunction<(PluginContext, Value), ()>>,
pub load: Option<ThreadsafeFunction<(PluginContext, String), Option<LoadResult>>>,
pub load_include: Option<ThreadsafeFunction<(PluginContext, String), Option<bool>>>,
pub watch_changes: Option<ThreadsafeFunction<(PluginContext, String, WatchChangesParams), ()>>,
pub resolve_id: Option<ThreadsafeFunction<ResolveIdFuncParams, Option<ResolveIdResult>>>,
pub _on_generate_file: Option<ThreadsafeFunction<(PluginContext, WriteFile), ()>>,
pub transform:
Option<ThreadsafeFunction<(PluginContext, String, String), Option<TransformResult>>>,
pub transform_include: Option<ThreadsafeFunction<(PluginContext, String), Option<bool>>>,
}

impl TsFnHooks {
Expand Down
119 changes: 93 additions & 26 deletions crates/binding/src/js_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use mako::ast::file::{Content, JsContent};
use mako::compiler::Context;
use mako::plugin::{Plugin, PluginGenerateEndParams, PluginLoadParam, PluginResolveIdParams};
use mako::resolve::{ExternalResource, Resolution, ResolvedResource, ResolverResource};
use napi_derive::napi;

use crate::js_hook::{
LoadResult, ResolveIdParams, ResolveIdResult, TransformResult, TsFnHooks, WatchChangesParams,
Expand All @@ -27,6 +28,29 @@ fn content_from_result(result: TransformResult) -> Result<Content> {
}
}

#[napi]
pub struct PluginContext {
context: Arc<Context>,
}

#[napi]
impl PluginContext {
#[napi]
pub fn warn(&self, msg: String) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the warn method in PluginContext handles logging in a thread-safe manner if used in concurrent environments.

println!("WARN: {}", msg)
}
#[napi]
pub fn error(&self, msg: String) {
println!("ERROR: {}", msg)
}
#[napi]
pub fn emit_file(&self, origin_path: String, output_path: String) {
let mut assets_info = self.context.assets_info.lock().unwrap();
assets_info.insert(origin_path, output_path);
drop(assets_info);
}
}

pub struct JsPlugin {
pub hooks: TsFnHooks,
pub name: Option<String>,
Expand All @@ -42,27 +66,33 @@ impl Plugin for JsPlugin {
self.enforce.as_deref()
}

fn build_start(&self, _context: &Arc<Context>) -> Result<()> {
fn build_start(&self, context: &Arc<Context>) -> Result<()> {
if let Some(hook) = &self.hooks.build_start {
hook.call(())?
hook.call(PluginContext {
context: context.clone(),
})?
}
Ok(())
}

fn load(&self, param: &PluginLoadParam, _context: &Arc<Context>) -> Result<Option<Content>> {
fn load(&self, param: &PluginLoadParam, context: &Arc<Context>) -> Result<Option<Content>> {
if let Some(hook) = &self.hooks.load {
if self.hooks.load_include.is_some()
&& self
.hooks
.load_include
.as_ref()
.unwrap()
.call(param.file.path.to_string_lossy().to_string())?
== Some(false)
&& self.hooks.load_include.as_ref().unwrap().call((
PluginContext {
context: context.clone(),
},
param.file.path.to_string_lossy().to_string(),
))? == Some(false)
{
return Ok(None);
}
let x: Option<LoadResult> = hook.call(param.file.path.to_string_lossy().to_string())?;
let x: Option<LoadResult> = hook.call((
PluginContext {
context: context.clone(),
},
param.file.path.to_string_lossy().to_string(),
))?;
if let Some(x) = x {
return content_from_result(TransformResult {
content: x.content,
Expand All @@ -79,10 +109,13 @@ impl Plugin for JsPlugin {
source: &str,
importer: &str,
params: &PluginResolveIdParams,
_context: &Arc<Context>,
context: &Arc<Context>,
) -> Result<Option<ResolverResource>> {
if let Some(hook) = &self.hooks.resolve_id {
let x: Option<ResolveIdResult> = hook.call((
PluginContext {
context: context.clone(),
},
source.to_string(),
importer.to_string(),
ResolveIdParams {
Expand Down Expand Up @@ -110,21 +143,31 @@ impl Plugin for JsPlugin {
Ok(None)
}

fn generate_end(&self, param: &PluginGenerateEndParams, _context: &Arc<Context>) -> Result<()> {
fn generate_end(&self, param: &PluginGenerateEndParams, context: &Arc<Context>) -> Result<()> {
// keep generate_end for compatibility
// since build_end does not have none error params in unplugin's api spec
if let Some(hook) = &self.hooks.generate_end {
hook.call(serde_json::to_value(param)?)?
hook.call((
PluginContext {
context: context.clone(),
},
serde_json::to_value(param)?,
))?
}
if let Some(hook) = &self.hooks.build_end {
hook.call(())?
hook.call(PluginContext {
context: context.clone(),
})?
}
Ok(())
}

fn watch_changes(&self, id: &str, event: &str, _context: &Arc<Context>) -> Result<()> {
fn watch_changes(&self, id: &str, event: &str, context: &Arc<Context>) -> Result<()> {
if let Some(hook) = &self.hooks.watch_changes {
hook.call((
PluginContext {
context: context.clone(),
},
id.to_string(),
WatchChangesParams {
event: event.to_string(),
Expand All @@ -134,19 +177,31 @@ impl Plugin for JsPlugin {
Ok(())
}

fn write_bundle(&self, _context: &Arc<Context>) -> Result<()> {
fn write_bundle(&self, context: &Arc<Context>) -> Result<()> {
if let Some(hook) = &self.hooks.write_bundle {
hook.call(())?
hook.call(PluginContext {
context: context.clone(),
})?
}
Ok(())
}

fn before_write_fs(&self, path: &std::path::Path, content: &[u8]) -> Result<()> {
fn before_write_fs(
&self,
path: &std::path::Path,
content: &[u8],
context: &Arc<Context>,
) -> Result<()> {
if let Some(hook) = &self.hooks._on_generate_file {
hook.call(WriteFile {
path: path.to_string_lossy().to_string(),
content: content.to_vec(),
})?;
hook.call((
PluginContext {
context: context.clone(),
},
WriteFile {
path: path.to_string_lossy().to_string(),
content: content.to_vec(),
},
))?;
}
Ok(())
}
Expand All @@ -155,10 +210,16 @@ impl Plugin for JsPlugin {
&self,
content: &mut Content,
path: &str,
_context: &Arc<Context>,
context: &Arc<Context>,
) -> Result<Option<Content>> {
if let Some(hook) = &self.hooks.transform_include {
if hook.call(path.to_string())? == Some(false) {
if hook.call((
PluginContext {
context: context.clone(),
},
path.to_string(),
))? == Some(false)
{
return Ok(None);
}
}
Expand All @@ -170,7 +231,13 @@ impl Plugin for JsPlugin {
_ => return Ok(None),
};

let result: Option<TransformResult> = hook.call((content_str, path.to_string()))?;
let result: Option<TransformResult> = hook.call((
PluginContext {
context: context.clone(),
},
content_str,
path.to_string(),
))?;

if let Some(result) = result {
return content_from_result(result).map(Some);
Expand Down
10 changes: 8 additions & 2 deletions crates/mako/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,12 @@ pub trait Plugin: Any + Send + Sync {
Ok(())
}

fn before_write_fs(&self, _path: &Path, _content: &[u8]) -> Result<()> {
fn before_write_fs(
&self,
_path: &Path,
_content: &[u8],
_context: &Arc<Context>,
) -> Result<()> {
Ok(())
}

Expand Down Expand Up @@ -422,9 +427,10 @@ impl PluginDriver {
&self,
path: P,
content: C,
context: &Arc<Context>,
) -> Result<()> {
for p in &self.plugins {
p.before_write_fs(path.as_ref(), content.as_ref())?;
p.before_write_fs(path.as_ref(), content.as_ref(), context)?;
}

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion crates/mako/src/plugins/bundless_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl BundlessCompiler {

self.context
.plugin_driver
.before_write_fs(&to, content.as_ref())
.before_write_fs(&to, content.as_ref(), &self.context)
.unwrap();

if !self.context.config.output.skip_write {
Expand Down
14 changes: 8 additions & 6 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,6 @@ Notice: When using `"node"`, you also need to set `dynamicImportToRequire` to `t
Specify the plugins to use.

```ts
// JSHooks
{
name?: string;
enforce?: "pre" | "post";
Expand All @@ -583,12 +582,15 @@ Specify the plugins to use.
}
```

JSHooks is a set of hook functions used to extend the compilation process of Mako.
And you can also use this methods in hook functions.

- `name`, plugin name
- `buildStart`, called before Build starts
- `load`, used to load files, return file content and type, type supports `css`, `js`, `jsx`, `ts`, `tsx`
- `generateEnd`, called after Generate completes, `isFirstCompile` can be used to determine if it is the first compilation, `time` is the compilation time, and `stats` is the compilation statistics information
- `this.emitFile({ type: 'asset', fileName: string, source: string | Uint8Array })`, emit a file
- `this.warn(message: string)`, emit a warning
- `this.error(message: string)`, emit a error
- `this.parse(code: string)`, parse the code (CURRENTLY NOT SUPPORTED)
- `this.addWatchFile(filePath: string)`, add a watch file (CURRENTLY NOT SUPPORTED)

Plugins is compatible with [unplugin](https://unplugin.unjs.io/), so you can use plugins from unplugin like [unplugin-icons](https://github.com/unplugin/unplugin-icons), [unplugin-replace](https://github.com/unplugin/unplugin-replace) and so on.

### progress

Expand Down
14 changes: 8 additions & 6 deletions docs/config.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,6 @@ import(/* webpackIgnore: true */ "./foo");
指定使用的插件。

```ts
// JSHooks
{
name?: string;
enforce?: "pre" | "post";
Expand All @@ -581,12 +580,15 @@ import(/* webpackIgnore: true */ "./foo");
}
```

JSHooks 是一组用来扩展 Mako 编译过程的钩子函数
你还可以在 hook 函数里用以下方法

- `name`,插件名称
- `buildStart`,构建开始前调用
- `load`,用于加载文件,返回文件内容和类型,类型支持 `css`、`js`、`jsx`、`ts`、`tsx`
- `generateEnd`,生成完成后调用,`isFirstCompile` 可用于判断是否为首次编译,`time` 为编译时间,`stats` 是编译统计信息
- `this.emitFile({ type: 'asset', fileName: string, source: string | Uint8Array })`, 添加文件到输出目录
- `this.warn(message: string)`, 添加一个警告
- `this.error(message: string)`, 添加一个错误
- `this.parse(code: string)`, 解析代码 (CURRENTLY NOT SUPPORTED)
- `this.addWatchFile(filePath: string)`, 添加一个监听文件 (CURRENTLY NOT SUPPORTED)

Plugins 兼容 [unplugin](https://unplugin.unjs.io/),所以你可以使用 unplugin 的插件,比如 [unplugin-icons](https://github.com/unplugin/unplugin-icons), [unplugin-replace](https://github.com/unplugin/unplugin-replace) 等。

### progress

Expand Down
7 changes: 7 additions & 0 deletions e2e/fixtures/plugins.context/expect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const assert = require("assert");

const { parseBuildResult, trim, moduleReg } = require("../../../scripts/test-utils");
const { files } = parseBuildResult(__dirname);

const content = files["index.js"];
assert.strictEqual(files['test.txt'], 'test');
6 changes: 6 additions & 0 deletions e2e/fixtures/plugins.context/mako.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"plugins": [
"./plugin"
],
"minify": false
}
6 changes: 6 additions & 0 deletions e2e/fixtures/plugins.context/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

module.exports = {
async load(path) {
path.endsWith('.hoo');
}
};
27 changes: 27 additions & 0 deletions e2e/fixtures/plugins.context/plugins.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module.exports = [
{
async loadInclude(path) {
// this.warn('loadInclude: ' + path);
path.endsWith('.hoo');
return true;
},
async load(path) {
if (path.endsWith('.hoo')) {
this.warn('load: ' + path);
this.warn({
message: 'test warn with object',
});
this.error('error: ' + path);
this.emitFile({
type: 'asset',
fileName: 'test.txt',
source: 'test',
});
return {
content: `export default () => <Foooo>.hoo</Foooo>;`,
type: 'jsx',
};
}
}
},
];
1 change: 1 addition & 0 deletions e2e/fixtures/plugins.context/src/foo.hoo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// should be handled with load hook
1 change: 1 addition & 0 deletions e2e/fixtures/plugins.context/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(require('./foo.hoo'));
Loading
Loading