Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESM related fixes #897

Merged
merged 7 commits into from
Oct 11, 2024
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
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ bundle.js
.all-contributorsrc
.editorconfig
.gitignore
bundle-model.mjs
jest.config.js
tsconfig.cjs.json
tsconfig.esm.json
Expand Down
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,25 +113,24 @@ You can also use same parameter and load the model from your website/server, as
Model MobileNetV2 - [224x224](https://github.com/infinitered/nsfwjs/blob/master/models/mobilenet_v2/)

```js
const model = nsfwjs.load("/path/to/model/directory/");
const model = nsfwjs.load("/path/to/mobilenet_v2/");
```

If you're using a model that needs an image of dimension other than 224x224, you can pass the size in the options parameter.

Model MobileNetV2Mid - [299x299](https://github.com/infinitered/nsfwjs/tree/master/models/mobilenet_v2_mid)
Model MobileNetV2Mid - [Graph](https://github.com/infinitered/nsfwjs/tree/master/models/mobilenet_v2_mid)

```js
const model = nsfwjs.load("/path/to/model/", { size: 299 });
/* You may need to load this model with graph type */
const model = nsfwjs.load("/path/to/model/", { type: 'graph' });
const model = nsfwjs.load("/path/to/mobilenet_v2_mid/", { type: 'graph' });
```

If you're using a graph model, you cannot use the infer method, and you'll need to tell model load that you're dealing with a graph model in options.

Model InceptionV3 - [Graph](https://github.com/infinitered/nsfwjs/tree/master/models/inception_v3)
Model InceptionV3 - [299x299](https://github.com/infinitered/nsfwjs/tree/master/models/inception_v3)

```js
const model = nsfwjs.load("/path/to/different/model/", { type: "graph" });
const model = nsfwjs.load("/path/to/inception_v3/", { size: 299 });
```

### Caching
Expand Down Expand Up @@ -226,13 +225,13 @@ Here is how to install the default model on a website:

### Tensorflow.js in the browser

The demo that powers https://nsfwjs.com/ is available in the `examples/nsfw_demo` folder.
The demo that powers https://nsfwjs.com/ is available in the [`examples/nsfw_demo`](https://github.com/infinitered/nsfwjs/tree/master/examples/nsfw_demo) folder.

To run the demo, run `yarn prep` which will copy the latest code into the demo. After that's done, you can `cd` into the demo folder and run with `yarn start`.

### Browserify

A browserified version using nothing but promises and script tags is available in the `minimal_demo` folder.
A browserified version using nothing but promises and script tags is available in the [`minimal_demo`](https://github.com/infinitered/nsfwjs/tree/master/examples/minimal_demo) folder.

```js
<script src="/path/to/model/directory/model.min.js"></script>
Expand Down
6 changes: 3 additions & 3 deletions examples/minimal_demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
<!-- <script src="https://unpkg.com/[email protected]" type="text/javascript"></script> -->
<!-- For testing: Load from local bundle `yarn bundle` -->
<script
src="../../dist/models/mobilenet_v2_mid/model.min.js"
src="../../dist/cjs/models/mobilenet_v2_mid/model.min.js"
type="text/javascript"
></script>
<script
src="../../dist/models/mobilenet_v2_mid/group1-shard1of2.min.js"
src="../../dist/cjs/models/mobilenet_v2_mid/group1-shard1of2.min.js"
type="text/javascript"
></script>
<script
src="../../dist/models/mobilenet_v2_mid/group1-shard2of2.min.js"
src="../../dist/cjs/models/mobilenet_v2_mid/group1-shard2of2.min.js"
type="text/javascript"
></script>
<script src="../../dist/browser/nsfwjs.min.js" type="text/javascript"></script>
Expand Down
16 changes: 10 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "nsfwjs",
"version": "4.1.0",
"unpkg": "dist/nsfwjs.min.js",
"unpkg": "dist/browser/nsfwjs.min.js",
"description": "Detect NSFW content client-side",
"type": "commonjs",
"module": "dist/esm/index.js",
"main": "dist/cjs/index.js",
"exports": {
Expand All @@ -11,9 +12,9 @@
},
"scripts": {
"lint": "tslint -p . -t verbose",
"build:esm": "tsc -p ./tsconfig.esm.json",
"build:esm": "tsc -p ./tsconfig.esm.json && node ./scripts/add-extensions.mjs",
"build:cjs": "tsc -p ./tsconfig.cjs.json",
"build": "yarn build:esm && yarn build:cjs",
"build": "yarn build:esm && yarn build:cjs && node ./scripts/add-nested-package-json.mjs",
"bundle:mobilenet_v2": "node ./scripts/bundle-model.mjs mobilenet_v2 1",
"bundle:mobilenet_v2_mid": "node ./scripts/bundle-model.mjs mobilenet_v2_mid 2",
"bundle:inception_v3": "node ./scripts/bundle-model.mjs inception_v3 6",
Expand All @@ -36,9 +37,9 @@
"type": "git",
"url": "git+https://github.com/infinitered/nsfwjs.git"
},
"dependencies": {},
"peerDependencies": {
"@tensorflow/tfjs": "^4.0.0"
"@tensorflow/tfjs": "^4.0.0",
"buffer": "^6.0.3"
},
"devDependencies": {
"@tensorflow/tfjs": "^4.0.0",
Expand All @@ -47,7 +48,9 @@
"babel-core": "^6.26.3",
"babel-plugin-transform-runtime": "~6.23.0",
"browserify": "^17.0.0",
"buffer": "^6.0.3",
"doctoc": "^2.2.0",
"esbuild": "^0.24.0",
"jest": "^28.1.3",
"jpeg-js": "^0.4.4",
"np": "^10.0.0",
Expand All @@ -73,5 +76,6 @@
"bugs": {
"url": "https://github.com/infinitered/nsfwjs/issues"
},
"homepage": "https://nsfwjs.com"
"homepage": "https://nsfwjs.com",
"dependencies": {}
}
40 changes: 40 additions & 0 deletions scripts/add-extensions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
import { join } from "path";

const directory = "./dist/esm"; // Target directory
const extension = ".js";

function addExtensions(dir) {
const files = readdirSync(dir);

files.forEach((file) => {
const fullPath = join(dir, file);

if (statSync(fullPath).isDirectory()) {
addExtensions(fullPath);
} else if (fullPath.endsWith(".js")) {
let content = readFileSync(fullPath, "utf8");

// Add .js extension to static imports (from './path')
content = content.replace(/(from\s+['"]\.\/[^'"]+)/g, (match) => {
if (!match.endsWith(extension)) {
return `${match}${extension}`;
}
return match;
});

// Add .js extension to dynamic imports (import('./path'))
content = content.replace(/(import\(['"]\.\/[^'"]+)/g, (match) => {
if (!match.endsWith(extension)) {
return `${match}${extension}`;
}
return match;
});

writeFileSync(fullPath, content);
}
});
}

// Call the function on the target directory
addExtensions(directory);
13 changes: 13 additions & 0 deletions scripts/add-nested-package-json.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { writeFileSync } from "fs";
import { join } from "path";

function addPackageJson(dir, type) {
const packageJsonContent = JSON.stringify({ type }, null, 2);
const filePath = join(dir, "package.json");
writeFileSync(filePath, packageJsonContent);
console.log(`Created package.json in ${dir}`);
}

// Add package.json for ESM and CJS builds
addPackageJson("./dist/esm", "module");
addPackageJson("./dist/cjs", "commonjs");
110 changes: 61 additions & 49 deletions scripts/bundle-model.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,74 +6,86 @@ import {
unlinkSync,
writeFileSync,
} from "fs";
import { promisify } from "util";

// The first argument is the model name, the second argument is the number of shards
const execPromise = promisify(exec);
const model = process.argv[2];
const numShards = +process.argv[3];
const filenames = ["model"];
const filenames = [
"model",
...Array.from(
{ length: numShards },
(_, i) => `group1-shard${i + 1}of${numShards}`
),
];

for (let i = 1; i <= numShards; i++) {
filenames.push(`group1-shard${i}of${numShards}`);
}
const BASE_MODEL_PATH = "./models/";
const BASE_DIST_PATH = "./dist/";
const JSON_EXT = ".json";
const MIN_JS_EXT = ".min.js";

const binToJson = (sourcePath, outputPath) => {
// Read the binary file and convert it to Base64
const binFile = readFileSync(`${sourcePath}`);
const base64String = binFile.toString("base64");

// Write the Base64 string to a .json file
writeFileSync(`${outputPath}.json`, JSON.stringify(base64String));
};

filenames.forEach((filename) => {
const sourcePath = `./models/${model}/${filename}`;
const outputDir = `./dist/models/${model}`;
const runCommand = async (command) => {
try {
const { stdout, stderr } = await execPromise(command);
if (stderr) console.error(`stderr: ${stderr}`);
if (stdout) console.log(`stdout: ${stdout}`);
} catch (error) {
console.error(`Error: ${error.message}`);
}
};

const generateUmd = async (outputPath, filename) => {
const identifier = filename.replace(/-/g, "_");
await runCommand(
`browserify ${outputPath}.json -s ${identifier} -o ${outputPath}.js`
);
await runCommand(
`terser ${outputPath}.js -c -m -o ${outputPath}${MIN_JS_EXT}`
);
unlinkSync(`${outputPath}.js`);
unlinkSync(`${outputPath}.json`);
};

const generateEsm = async (outputPath) => {
await runCommand(
`esbuild ${outputPath}.json --bundle --format=esm --minify --outfile=${outputPath}${MIN_JS_EXT} --log-level=error`
);
unlinkSync(`${outputPath}.json`);
};

const processBundle = async (filename, type) => {
const sourcePath = `${BASE_MODEL_PATH}${model}/${filename}`;
const outputDir = `${BASE_DIST_PATH}${type}/models/${model}`;
const outputPath = `${outputDir}/${filename}`;

// Create the output directory if it doesn't exist
mkdirSync(outputDir, { recursive: true });

if (filename === "model") {
// Copy the model.json file
copyFileSync(`${sourcePath}.json`, `${outputPath}.json`);
copyFileSync(`${sourcePath}${JSON_EXT}`, `${outputPath}${JSON_EXT}`);
} else {
binToJson(sourcePath, outputPath);
}

// Browserify the .json file
const identifier = filename.replace(/-/g, "_");
exec(
`browserify ${outputPath}.json -s ${identifier} -o ${outputPath}.js`,
(error, stdout, stderr) => {
if (error) {
console.log(`Error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);

// Minify the .js file with Terser
exec(
`terser ${outputPath}.js -c -m -o ${outputPath}.min.js`,
(error, stdout, stderr) => {
if (error) {
console.log(`Error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
if (type === "cjs") {
await generateUmd(outputPath, filename);
} else if (type === "esm") {
await generateEsm(outputPath);
}
};

// Delete the original .js and .json files
unlinkSync(`${outputPath}.js`);
unlinkSync(`${outputPath}.json`);
}
);
}
const bundleAll = async () => {
await Promise.all(
filenames.map(async (filename) => {
await processBundle(filename, "cjs");
await processBundle(filename, "esm");
})
);
});
};

bundleAll().catch((err) => console.error(err));
21 changes: 3 additions & 18 deletions scripts/post-test.mjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
import { readdirSync, unlinkSync } from "fs";
import { join } from "path";
import { rmSync } from "fs";

const dir = "./models";
const dir = "./src/models";

// Get the names of all subfolders in the dir
const models = readdirSync(dir, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);

models.forEach((model) => {
const modelDir = join(dir, model);

readdirSync(modelDir, { withFileTypes: true })
.filter((dirent) => dirent.isFile() && dirent.name.endsWith(".min.js"))
.forEach((dirent) => {
const file = join(modelDir, dirent.name);
unlinkSync(file);
});
});
rmSync(dir, { recursive: true, force: true });
8 changes: 5 additions & 3 deletions scripts/pre-test.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { copyFileSync, readdirSync } from "fs";
import { copyFileSync, mkdirSync, readdirSync } from "fs";
import { join } from "path";

const srcDir = "./dist/models";
const destDir = "./models";
const srcDir = "./dist/cjs/models";
const destDir = "./src/models";

// Get the names of all subfolders in the srcDir
const models = readdirSync(srcDir, { withFileTypes: true })
Expand All @@ -13,6 +13,8 @@ models.forEach((model) => {
const modelSrcDir = join(srcDir, model);
const modelDestDir = join(destDir, model);

mkdirSync(modelDestDir, { recursive: true });

readdirSync(modelSrcDir, { withFileTypes: true })
.filter((dirent) => dirent.isFile() && dirent.name.endsWith(".min.js"))
.forEach((dirent) => {
Expand Down
Loading