diff --git a/README.md b/README.md
index 8aba0aa29..795718561 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,11 @@ Started in 68ms and updated in 1ms for a demo react project as below.
![img](./assets/performance.png)
**Features**:
-* 🔥 **Super Fast**: Start a react / vue(incoming) project in milliseconds.
-* ⚡ **"1ms" HMR**: Finish a HMR within 10ms for the most situations.
-* 🧰 **Fully Pluggable**: Support both rust plugins and js plugins.
-* ⚙️ **Native Web Assets Compiling Supported**: Support support compiling JS/TS/JSX/TSX, css, html natively.
+
+- 🔥 **Super Fast**: Start a react / vue(incoming) project in milliseconds.
+- ⚡ **"1ms" HMR**: Finish a HMR within 10ms for the most situations.
+- 🧰 **Fully Pluggable**: Support both rust plugins and js plugins.
+- ⚙️ **Native Web Assets Compiling Supported**: Support support compiling JS/TS/JSX/TSX, css, html natively.
@@ -32,7 +33,9 @@ Started in 68ms and updated in 1ms for a demo react project as below.
## Getting Started
+
Install Farm Cli:
+
```sh
npm install -g @farmfe/cli
```
@@ -42,3 +45,113 @@ We provided a experience react project for now. Using `farm create` to create a
```sh
farm create && cd farm-react && npm i && npm start
```
+
+## Configuring
+
+> Official docs site is on the way...
+
+Farm load configuration file from `farm.config.ts`. The available config as below:
+
+```ts
+export interface UserConfig {
+ /** current root of this project, default to current working directory */
+ root?: string;
+ /** js plugin(which is a javascript object) and rust plugin(which is string refer to a .farm file or a package) */
+ plugins?: (RustPlugin | JsPlugin)[];
+ /** config related to compilation */
+ compilation?: {
+ input?: Record;
+ output?: {
+ filename?: string;
+ path?: string;
+ publicPath?: string;
+ };
+ resolve?: {
+ extensions?: string[];
+ alias?: Record;
+ mainFields?: string[];
+ conditions?: string[];
+ symlinks: boolean;
+ };
+ external?: string[];
+ mode?: 'development' | 'production';
+ root?: string;
+ runtime?: {
+ path: string;
+ plugins?: string[];
+ swcHelpersPath?: string;
+ };
+ script?: {
+ // specify target es version
+ target?:
+ | 'es3'
+ | 'es5'
+ | 'es2015'
+ | 'es2016'
+ | 'es2017'
+ | 'es2018'
+ | 'es2019'
+ | 'es2020'
+ | 'es2021'
+ | 'es2022';
+ // config swc parser
+ parser?: {
+ esConfig?: {
+ jsx?: boolean;
+ fnBind: boolean;
+ // Enable decorators.
+ decorators: boolean;
+
+ // babel: `decorators.decoratorsBeforeExport`
+ //
+ // Effective only if `decorator` is true.
+ decoratorsBeforeExport: boolean;
+ exportDefaultFrom: boolean;
+ // Stage 3.
+ importAssertions: boolean;
+ privateInObject: boolean;
+ allowSuperOutsideMethod: boolean;
+ allowReturnOutsideFunction: boolean;
+ };
+ tsConfig?: {
+ tsx: boolean;
+ decorators: boolean;
+ /// `.d.ts`
+ dts: boolean;
+ noEarlyErrors: boolean;
+ };
+ };
+ };
+ partialBundling?: {
+ moduleBuckets?: {
+ name: string;
+ test: string[];
+ }[];
+ };
+ lazyCompilation?: boolean;
+ };
+ /** config related to dev server */
+ server?: UserServerConfig;
+}
+
+export type RustPlugin =
+ | string
+ | [
+ string,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ Record
+ ];
+
+export interface JsPlugin {
+ resolve: JsPluginHook<
+ {
+ importers: string[];
+ specifiers: string[];
+ },
+ PluginResolveHookParam,
+ PluginResolveHookResult
+ >;
+
+ // load: JsPluginHook<{ filters: { ids: string[] }}>;
+}
+```
diff --git a/crates/compiler/src/generate/finalize_resources.rs b/crates/compiler/src/generate/finalize_resources.rs
index 0e58bb4d0..09bda9a5c 100644
--- a/crates/compiler/src/generate/finalize_resources.rs
+++ b/crates/compiler/src/generate/finalize_resources.rs
@@ -1,17 +1,76 @@
-use std::sync::Arc;
+use std::{
+ fs::{create_dir_all, read_dir, remove_file, File},
+ io::Write,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
-use farmfe_core::context::CompilationContext;
+use farmfe_core::{context::CompilationContext, relative_path::RelativePath};
use farmfe_toolkit::tracing;
#[tracing::instrument(skip_all)]
pub fn finalize_resources(context: &Arc) -> farmfe_core::error::Result<()> {
tracing::trace!("Staring finalize_resources...");
- let mut resources_map = context.resources_map.lock();
- context
- .plugin_driver
- .finalize_resources(&mut *resources_map, context)?;
+ {
+ let mut resources_map = context.resources_map.lock();
+
+ context
+ .plugin_driver
+ .finalize_resources(&mut *resources_map, context)?;
+ }
+
+ write_resources(context);
tracing::trace!("Finished finalize_resources.");
Ok(())
}
+
+pub fn write_resources(context: &Arc) {
+ let resources = context.resources_map.lock();
+ let output_dir = if Path::new(&context.config.output.path).is_absolute() {
+ PathBuf::from(&context.config.output.path)
+ } else {
+ RelativePath::new(&context.config.output.path).to_logical_path(&context.config.root)
+ };
+
+ if !output_dir.exists() {
+ create_dir_all(output_dir.clone()).unwrap();
+ }
+
+ // Remove useless resources
+ let existing_resources = read_dir(output_dir.clone())
+ .unwrap()
+ .map(|entry| {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ let file_name = path.file_name().unwrap().to_str().unwrap().to_string();
+
+ file_name
+ })
+ .collect::>();
+
+ for pre_resource in &existing_resources {
+ let file_path = RelativePath::new(pre_resource).to_logical_path(&output_dir);
+ // always remove html file
+ if pre_resource.ends_with(".html") {
+ remove_file(file_path).unwrap();
+ continue;
+ }
+
+ if !resources.contains_key(pre_resource) && file_path.exists() {
+ remove_file(file_path).unwrap();
+ }
+ }
+
+ // add new resources
+ for resource in resources.values() {
+ let file_path = RelativePath::new(&resource.name).to_logical_path(&output_dir);
+ // only write expose non-emitted resource
+ if !resource.emitted && !file_path.exists() {
+ let mut file = File::create(file_path).unwrap();
+
+ file.write_all(&resource.bytes).unwrap();
+ }
+ }
+}
diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs
index 90121276a..882e71aa5 100644
--- a/crates/node/src/lib.rs
+++ b/crates/node/src/lib.rs
@@ -1,6 +1,11 @@
#![deny(clippy::all)]
-use std::{collections::HashMap, sync::Arc};
+use std::{
+ collections::HashMap,
+ fs::remove_dir_all,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
use farmfe_compiler::{update::UpdateType, Compiler};
@@ -9,6 +14,7 @@ pub mod plugin_adapters;
use farmfe_core::{
config::{Config, Mode},
module::ModuleId,
+ relative_path::RelativePath,
};
use farmfe_toolkit::tracing_subscriber::{self, fmt, prelude::*, EnvFilter};
use napi::{bindgen_prelude::FromNapiValue, Env, JsObject, NapiRaw, Status};
@@ -108,19 +114,34 @@ impl JsCompiler {
/// TODO: usage example
#[napi]
pub async fn compile(&self) -> napi::Result<()> {
+ let context = self.compiler.context();
+ let output_dir = if Path::new(&context.config.output.path).is_absolute() {
+ PathBuf::from(&context.config.output.path)
+ } else {
+ RelativePath::new(&context.config.output.path).to_logical_path(&context.config.root)
+ };
+
+ if output_dir.exists() {
+ remove_dir_all(&output_dir).map_err(|e| {
+ napi::Error::new(
+ Status::GenericFailure,
+ format!("remove output dir error: {}", e),
+ )
+ })?;
+ }
+
self
.compiler
.compile()
- .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e)))
+ .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e)))?;
+
+ Ok(())
}
/// sync compile
#[napi]
pub fn compile_sync(&self) -> napi::Result<()> {
- self
- .compiler
- .compile()
- .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e)))
+ unimplemented!("sync compile is not supported yet")
}
/// async update, return promise
@@ -179,23 +200,6 @@ impl JsCompiler {
unimplemented!("sync update");
}
- #[napi]
- pub fn resources(&self) -> HashMap> {
- let context = self.compiler.context();
- let resources = context.resources_map.lock();
-
- let mut result = HashMap::new();
-
- for resource in resources.values() {
- // only write expose non-emitted resource
- if !resource.emitted {
- result.insert(resource.name.clone(), resource.bytes.clone());
- }
- }
-
- result
- }
-
#[napi]
pub fn has_module(&self, resolved_path: String) -> bool {
let context = self.compiler.context();
diff --git a/crates/plugin_partial_bundling/src/lib.rs b/crates/plugin_partial_bundling/src/lib.rs
index 44eca4180..c5bf30bc2 100644
--- a/crates/plugin_partial_bundling/src/lib.rs
+++ b/crates/plugin_partial_bundling/src/lib.rs
@@ -152,18 +152,6 @@ impl Plugin for FarmPluginPartialBundling {
.as_bytes(),
8,
);
- // let id = format!(
- // "{}-{}-{}-{}",
- // module_bucket.id.to_string(),
- // module_type.to_string(),
- // module_ids
- // .iter()
- // .map(|m| m.to_string())
- // .collect::>()
- // .join("_"),
- // immutable
- // )
- // .replace("/", "+");
let mut resource_pot = ResourcePot::new(ResourcePotId::new(id), module_type.into());
resource_pot.immutable = immutable;
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index c5e88664c..137ff8add 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,14 @@
# @farmfe/core
+## 0.3.2
+
+### Patch Changes
+
+- write resources to disk to optimize resources loading time
+- Updated dependencies
+ - @farmfe/runtime-plugin-hmr@3.0.2
+ - @farmfe/runtime@0.3.2
+
## 0.3.1
### Patch Changes
diff --git a/packages/core/binding/binding.d.ts b/packages/core/binding/binding.d.ts
index c6d866800..01d3f7ec9 100644
--- a/packages/core/binding/binding.d.ts
+++ b/packages/core/binding/binding.d.ts
@@ -41,6 +41,5 @@ export class Compiler {
update(paths: Array): Promise;
/** sync update */
updateSync(paths: Array): JsUpdateResult;
- resources(): Record>;
hasModule(resolvedPath: string): boolean;
}
diff --git a/packages/core/package.json b/packages/core/package.json
index ccc4f9d36..4eb642eb7 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@farmfe/core",
- "version": "0.3.1",
+ "version": "0.3.2",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
@@ -61,8 +61,8 @@
"type-check": "tsc -p tsconfig.build.json --noEmit"
},
"dependencies": {
- "@farmfe/runtime": "workspace:^0.3.1",
- "@farmfe/runtime-plugin-hmr": "workspace:^3.0.1",
+ "@farmfe/runtime": "workspace:^0.3.2",
+ "@farmfe/runtime-plugin-hmr": "workspace:^3.0.2",
"@swc/helpers": "^0.4.9",
"boxen": "^7.0.1",
"chalk": "^5.2.0",
diff --git a/packages/core/src/compiler/index.ts b/packages/core/src/compiler/index.ts
index a38e54cef..16208952f 100644
--- a/packages/core/src/compiler/index.ts
+++ b/packages/core/src/compiler/index.ts
@@ -1,7 +1,3 @@
-import { existsSync, mkdirSync } from 'fs';
-import fs from 'fs/promises';
-import path from 'path';
-
import type { Config, JsUpdateResult } from '../../binding/index.js';
import { Compiler as BindingCompiler } from '../../binding/index.js';
@@ -48,28 +44,7 @@ export class Compiler {
return res;
}
- resources(): Record {
- return this._bindingCompiler.resources();
- }
-
hasModule(resolvedPath: string): boolean {
return this._bindingCompiler.hasModule(resolvedPath);
}
-
- async writeResourcesToDisk(): Promise {
- const resources = this.resources();
- const promises = [];
-
- for (const [name, resource] of Object.entries(resources)) {
- const filePath = path.join(this.config.config.output.path, name);
-
- if (!existsSync(path.dirname(filePath))) {
- mkdirSync(path.dirname(filePath), { recursive: true });
- }
-
- promises.push(fs.writeFile(filePath, Buffer.from(resource)));
- }
-
- await Promise.all(promises);
- }
}
diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts
index 9d9ea1083..0fb6b060d 100644
--- a/packages/core/src/config/index.ts
+++ b/packages/core/src/config/index.ts
@@ -133,7 +133,6 @@ export const DEFAULT_DEV_SERVER_OPTIONS: NormalizedServerConfig = {
port: 9000,
https: false,
// http2: false,
- writeToDisk: false,
hmr: DEFAULT_HMR_OPTIONS,
};
@@ -206,15 +205,21 @@ async function readConfigFile(
// if config is written in typescript, we need to compile it to javascript using farm first
if (resolvedPath.endsWith('.ts')) {
const Compiler = (await import('../compiler/index.js')).Compiler;
+ const outputPath = path.join(os.tmpdir(), 'farmfe');
+ const fileName = 'farm.config.bundle.mjs';
const normalizedConfig = await normalizeUserCompilationConfig({
compilation: {
input: {
config: resolvedPath,
},
+ output: {
+ filename: fileName,
+ path: outputPath,
+ },
partialBundling: {
moduleBuckets: [
{
- name: 'farm.config.bundle.js',
+ name: fileName,
test: ['.+'],
},
],
@@ -226,18 +231,9 @@ async function readConfigFile(
});
const compiler = new Compiler(normalizedConfig);
await compiler.compile();
- const resources = compiler.resources();
- // should only emit one config file bundled with all dependencies
- const configCode = Buffer.from(Object.values(resources)[0]).toString();
- // Change to vm.module of node or loaders as far as it is stable
- const filePath = path.join(
- os.tmpdir(),
- 'farmfe',
- `temp-config-${Date.now()}.mjs`
- );
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
- fs.writeFileSync(filePath, configCode);
+ const filePath = path.join(outputPath, fileName);
+ // Change to vm.module of node or loaders as far as it is stable
if (process.platform === 'win32') {
return (await import(pathToFileURL(filePath).toString())).default;
} else {
diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts
index 7e5da35c2..8afb9c39d 100644
--- a/packages/core/src/config/types.ts
+++ b/packages/core/src/config/types.ts
@@ -6,7 +6,6 @@ export interface UserServerConfig {
port?: number;
https?: boolean;
// http2?: boolean;
- writeToDisk?: boolean;
hmr?: boolean | UserHmrConfig;
}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index eb8f4aa6c..e9d48052e 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -58,5 +58,4 @@ export async function build(options: {
const compiler = new Compiler(normalizedConfig);
await compiler.compile();
logger.info(`Build completed in ${chalk.green(`${Date.now() - start}ms`)}!`);
- compiler.writeResourcesToDisk();
}
diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts
index f724f25e7..9c7d66be8 100644
--- a/packages/core/src/server/index.ts
+++ b/packages/core/src/server/index.ts
@@ -13,7 +13,6 @@ import {
NormalizedServerConfig,
normalizeDevServerOptions,
} from '../config/index.js';
-import { resources } from './middlewares/resources.js';
import { hmr } from './middlewares/hmr.js';
import { HmrEngine } from './hmr-engine.js';
import { brandColor, Logger } from '../logger.js';
@@ -49,11 +48,7 @@ export class DevServer {
mkdirSync(this._dist, { recursive: true });
}
- if (this.config.writeToDisk) {
- this._app.use(serve(this._dist));
- } else {
- this._app.use(resources(this._compiler));
- }
+ this._app.use(serve(this._dist));
if (this.config.hmr) {
this.ws = new WebSocketServer({
@@ -79,10 +74,6 @@ export class DevServer {
await this._compiler.compile();
const end = Date.now();
- if (this.config.writeToDisk) {
- this._compiler.writeResourcesToDisk();
- }
-
this._app.listen(this.config.port);
const version = JSON.parse(
readFileSync(
diff --git a/packages/core/src/server/middlewares/resources.ts b/packages/core/src/server/middlewares/resources.ts
deleted file mode 100644
index 87a4c382f..000000000
--- a/packages/core/src/server/middlewares/resources.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Serve resources that stored in memory. This middleware will be enabled when server.writeToDisk is false.
- */
-
-import { Context, Next } from 'koa';
-import { extname } from 'path';
-import { Compiler } from '../../compiler/index.js';
-
-export function resources(compiler: Compiler) {
- return async (ctx: Context, next: Next) => {
- await next();
-
- if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return;
- // the response is already handled
- if (ctx.body || ctx.status !== 404) return;
-
- const resourcePath = ctx.path.slice(1) || 'index.html'; // remove leading slash
- ctx.type = extname(resourcePath);
- const resource = compiler.resources()[resourcePath];
-
- if (!resource) return;
-
- ctx.body = Buffer.from(resource);
- };
-}
diff --git a/packages/core/tests/config.spec.ts b/packages/core/tests/config.spec.ts
index 9114e42de..264d94d49 100644
--- a/packages/core/tests/config.spec.ts
+++ b/packages/core/tests/config.spec.ts
@@ -37,10 +37,8 @@ test('normalize-dev-server-options', () => {
let options = normalizeDevServerOptions({});
expect(options.https).toBe(DEFAULT_DEV_SERVER_OPTIONS.https);
expect(options.port).toBe(DEFAULT_DEV_SERVER_OPTIONS.port);
- expect(options.writeToDisk).toBe(DEFAULT_DEV_SERVER_OPTIONS.writeToDisk);
- options = normalizeDevServerOptions({ writeToDisk: true });
+ options = normalizeDevServerOptions({ port: 8080 });
expect(options.https).toBe(DEFAULT_DEV_SERVER_OPTIONS.https);
- expect(options.port).toBe(DEFAULT_DEV_SERVER_OPTIONS.port);
- expect(options.writeToDisk).toBe(true);
+ expect(options.port).toBe(8080);
});
diff --git a/packages/runtime-plugin-hmr/CHANGELOG.md b/packages/runtime-plugin-hmr/CHANGELOG.md
index b8a42250c..efcbd3ed6 100644
--- a/packages/runtime-plugin-hmr/CHANGELOG.md
+++ b/packages/runtime-plugin-hmr/CHANGELOG.md
@@ -1,5 +1,11 @@
# @farmfe/runtime-plugin-hmr
+## 3.0.2
+
+### Patch Changes
+
+- write resources to disk to optimize resources loading time
+
## 3.0.1
### Patch Changes
diff --git a/packages/runtime-plugin-hmr/package.json b/packages/runtime-plugin-hmr/package.json
index 63eaa1cfe..0404b7e03 100644
--- a/packages/runtime-plugin-hmr/package.json
+++ b/packages/runtime-plugin-hmr/package.json
@@ -1,6 +1,6 @@
{
"name": "@farmfe/runtime-plugin-hmr",
- "version": "3.0.1",
+ "version": "3.0.2",
"description": "Runtime hmr plugin of Farm",
"author": {
"name": "bright wu",
@@ -11,6 +11,6 @@
"build": "tsc -p tsconfig.json --noEmit"
},
"devDependencies": {
- "@farmfe/runtime": "workspace:^0.3.1"
+ "@farmfe/runtime": "workspace:^0.3.2"
}
}
diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md
index b3d667150..c8d4ba82e 100644
--- a/packages/runtime/CHANGELOG.md
+++ b/packages/runtime/CHANGELOG.md
@@ -1,5 +1,11 @@
# @farmfe/runtime
+## 0.3.2
+
+### Patch Changes
+
+- write resources to disk to optimize resources loading time
+
## 0.3.1
### Patch Changes
diff --git a/packages/runtime/package.json b/packages/runtime/package.json
index 02ab2a344..08534f5d3 100644
--- a/packages/runtime/package.json
+++ b/packages/runtime/package.json
@@ -1,6 +1,6 @@
{
"name": "@farmfe/runtime",
- "version": "0.3.1",
+ "version": "0.3.2",
"description": "Runtime of Farm",
"author": {
"name": "bright wu",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6b5f8134c..8f6dd625b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -90,8 +90,8 @@ importers:
packages/core:
specifiers:
- '@farmfe/runtime': workspace:^0.3.1
- '@farmfe/runtime-plugin-hmr': workspace:^3.0.1
+ '@farmfe/runtime': workspace:^0.3.2
+ '@farmfe/runtime-plugin-hmr': workspace:^3.0.2
'@napi-rs/cli': ^2.10.0
'@swc/helpers': ^0.4.9
'@types/figlet': ^1.5.5
@@ -145,7 +145,7 @@ importers:
packages/runtime-plugin-hmr:
specifiers:
- '@farmfe/runtime': workspace:^0.3.1
+ '@farmfe/runtime': workspace:^0.3.2
devDependencies:
'@farmfe/runtime': link:../runtime