Skip to content

Commit

Permalink
JavaScriptLib Restructure (#4647)
Browse files Browse the repository at this point in the history
The goal of the pr is to allow existing Ts projects run with mill, it
takes into considerations the fluidity of Ts project structure and
removes the reliance on `ts-paths` as the only means to access `module`
dependencies.

This pr refactors `TscModule ` formerly (`TypeScriptModule `), it aims
to introduce the features:

- [x] `package` RootModule with TscModule
- [x] relative paths
- [x] esm support; use swc-core for compiling

Key changes:
- `compile` task has been modified, these modifications:
- change how sources, mod-sources, resources and generated sources are
grouped, allowing for the use of relative paths
- these changes also taking into consideration `RootModule` use, the top
level src feature now works along side `TscModule`
- Due to this restructuring, implementation of `PublishModule` has been
significantly altered as `TscModule` compilations output now prepares
source files internal and external (source files generated via a task
that would end up in the `out/`) in a manner that nodes `npm` publishing
tool will understand and work with.
- The restructuring also consequentially fixes the need for `TscModule`
to violate the new `writing to path during execution phase`
restrictions. As the previous implementation would require files (mostly
test config files and build scripts) to be created within or moved to
`compile.dest` during execution phases.
- It also provides minimal support of esm.

Additional examples
- [x] `module/7-root-module` for RootModule

base pr for #4425 @lihaoyi

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
monyedavid and autofix-ci[bot] authored Mar 10, 2025
1 parent 3f0e0e3 commit abdee90
Show file tree
Hide file tree
Showing 24 changed files with 890 additions and 592 deletions.
8 changes: 6 additions & 2 deletions example/javascriptlib/basic/3-custom-build-logic/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import mill._, javascriptlib._
object foo extends TypeScriptModule {

/** Total number of lines in module source files */
def lineCount = Task {
allSources().map(f => os.read.lines(f.path).size).sum
def lineCount: T[Int] = Task {
sources()
.flatMap(pathRef => os.walk(pathRef.path))
.filter(_.ext == "ts")
.map(os.read.lines(_).size)
.sum
}

/** Generate resource using lineCount of sources */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as fs from 'fs/promises';

const Resources: string = process.env.RESOURCESDEST || "@foo/resources.dest" // `RESOURCES` is generated on bundle
const Resources: string = process.env.RESOURCESDEST || "@foo/resources" // `RESOURCES` is generated on bundle
const LineCount = require.resolve(`${Resources}/line-count.txt`);

export default class Foo {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Foo from 'foo/foo';
import Foo from '../../../src/foo';

describe('Foo.getLineCount', () => {
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';

const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);

const server = http.createServer((req, res) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import express, {Express} from 'express';
import cors from 'cors';
import path from 'path'
import api from "./api"

const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);
const BuildPath = Client.replace(/index\.html$/, "");
const app: Express = express();
Expand Down
6 changes: 2 additions & 4 deletions example/javascriptlib/module/1-common-config/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import mill.javascriptlib._

object foo extends TypeScriptModule {

def customSource = Task {
Seq(PathRef(moduleDir / "custom-src/foo2.ts"))
}
def customSource = Task.Sources("custom-src/foo2.ts")

def allSources = super.allSources() ++ customSource()
def sources = Task { super.sources() ++ customSource() }

def resources = super.resources() ++ Seq(PathRef(moduleDir / "custom-resources"))

Expand Down
8 changes: 6 additions & 2 deletions example/javascriptlib/module/2-custom-tasks/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import mill._, javascriptlib._
object foo extends TypeScriptModule {

/** Total number of lines in module source files */
def lineCount = Task {
allSources().map(f => os.read.lines(f.path).size).sum
def lineCount: T[Int] = Task {
sources()
.flatMap(pathRef => os.walk(pathRef.path))
.filter(_.ext == "ts")
.map(os.read.lines(_).size)
.sum
}

def generatedSources = Task {
Expand Down
8 changes: 5 additions & 3 deletions example/javascriptlib/module/3-override-tasks/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package build
import mill._
import mill.javascriptlib._

object foo extends TypeScriptModule {
object `package` extends RootModule with TypeScriptModule {
def moduleName = "foo"

def sources = Task {
val srcPath = Task.dest / "src"
val filePath = srcPath / "foo.ts"
Expand All @@ -18,7 +20,7 @@ object foo extends TypeScriptModule {
""".stripMargin
)

PathRef(Task.dest)
Seq(PathRef(Task.dest))
}

def compile = Task {
Expand Down Expand Up @@ -46,7 +48,7 @@ object foo extends TypeScriptModule {

/** Usage

> mill foo.run "added tags"
> mill run "added tags"
Compiling...
Hello World!
Running... added tags
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion example/javascriptlib/module/5-resources/foo/src/foo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ if (process.env.NODE_ENV !== "test") {
console.error('Error:', err);
}
})();
}
}
51 changes: 51 additions & 0 deletions example/javascriptlib/module/7-root-module/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// You can use ``object `package` extends RootModule`` to use a `Module`
// as the root module of the file:

package build

import mill._, javascriptlib._

object `package` extends RootModule with TypeScriptModule {
def moduleName = "foo"
def npmDeps = Seq("[email protected]")
object test extends TypeScriptTests with TestModule.Jest
}

// Since our ``object `package` extends RootModule``, its files live in a
// top-level `src/` folder.

// Mill will ordinarily use the name of the singleton object, as
// the default value for the `moduleName` task.

// For exmaple:

// The `moduleName` for the singleton `bar` defined as
// `object bar extends TypeScriptModule` would be `bar`,
// with the expected source directory in `bar/src`.

// For this example, since we use the `RootModule` we would need to manually define our
// `moduleName`.
//
// The `moduleName` is used in the generated `tsconfig` files `compilerOptions['paths']`
// as the modules `src/` path mapping and to define the default `mainFileName`, which is
// the modules entry file.
// The generated mapping for this example would be `"foo/*": [...]`. and its expected main file
// woudld be `foo.ts` located in top-level `src/`

/** Usage
> mill run James Bond prof
Hello James Bond Professor

> mill test
PASS .../foo.test.ts
...
Test Suites:...1 passed, 1 total...
Tests:...3 passed, 3 total...
...

> mill show bundle
Build succeeded!

> node out/bundle.dest/bundle.js James Bond prof
Hello James Bond Professor
*/
33 changes: 33 additions & 0 deletions example/javascriptlib/module/7-root-module/src/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Map} from 'immutable';

interface User {
firstName: string
lastName: string
role: string
}

export const defaultRoles: Map<string, string> = Map({prof: "Professor"});

/**
* Generate a user object based on command-line arguments
* @param args Command-line arguments
* @returns User object
*/
export function generateUser(args: string[]): User {
return {
firstName: args[0] || "unknown",
lastName: args[1] || "unknown",
role: defaultRoles.get(args[2], ""),
};
}

// Main CLI logic
if (process.env.NODE_ENV !== "test") {
const args = process.argv.slice(2); // Skip 'node' and script name
const user = generateUser(args);

console.log(defaultRoles.toObject());
console.log(args[2]);
console.log(defaultRoles.get(args[2]));
console.log("Hello " + user.firstName + " " + user.lastName + " " + user.role);
}
64 changes: 64 additions & 0 deletions example/javascriptlib/module/7-root-module/test/src/foo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {generateUser, defaultRoles} from "foo/foo";
import {Map} from 'immutable';

// Define the type roles object
type RoleKeys = "admin" | "user";
type Roles = {
[key in RoleKeys]: string;
};

// Mock `defaultRoles` as a global variable for testing
const mockDefaultRoles = Map<string, string>({
admin: "Administrator",
user: "User",
});

describe("generateUser function", () => {
beforeAll(() => {
process.env.NODE_ENV = "test"; // Set NODE_ENV for all tests
});

afterEach(() => {
jest.clearAllMocks();
});

test("should generate a user with all specified fields", () => {
// Override the `defaultRoles` map for testing
(defaultRoles as any).get = mockDefaultRoles.get.bind(mockDefaultRoles);

const args = ["John", "Doe", "admin"];
const user = generateUser(args);

expect(user).toEqual({
firstName: "John",
lastName: "Doe",
role: "Administrator",
});
});

test("should default lastName and role when they are not provided", () => {
(defaultRoles as any).get = mockDefaultRoles.get.bind(mockDefaultRoles);

const args = ["Jane"];
const user = generateUser(args);

expect(user).toEqual({
firstName: "Jane",
lastName: "unknown",
role: "",
});
});

test("should default all fields when args is empty", () => {
(defaultRoles as any).get = mockDefaultRoles.get.bind(mockDefaultRoles);

const args: string[] = [];
const user = generateUser(args);

expect(user).toEqual({
firstName: "unknown",
lastName: "unknown",
role: "",
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Calculator} from 'bar/calculator';
import {Calculator} from '../../../src/calculator';

describe('Calculator', () => {
const calculator = new Calculator();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Calculator } from 'baz/calculator';
import {Calculator} from '../../../src/calculator';

describe('Calculator', () => {
const calculator = new Calculator();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express, {Express} from 'express';
import cors from 'cors';

const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);

const app: Express = express();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express, {Express} from 'express';
import cors from 'cors';

const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);

const app: Express = express();
Expand Down
Loading

0 comments on commit abdee90

Please sign in to comment.