tweaselORG / cyanoacrylate

Toolkit for large-scale automated traffic analysis of mobile apps on Android and iOS.
MIT License
5 stars 1 forks source link

`ERR_IMPORT_ASSERTION_TYPE_MISSING` when used without bundler #50

Open baltpeter opened 1 week ago

baltpeter commented 1 week ago

I tried upgrading cli to the latest CA version. However, when trying to build, I get the following error:

    TypeError Plugin: tweasel-cli Plugin: tweasel-cli: Module "file:///home/benni/coding/JS/tweasel/cli/node_modules/appstraction/package.json"
     needs an import attribute of type "json"
    Code: ERR_IMPORT_ASSERTION_TYPE_MISSING
baltpeter commented 1 week ago

Here's the code that causes this problem. We're trying to find appstraction's version by importing its package.json:

https://github.com/tweaselORG/cyanoacrylate/blob/46b03731254ad800e6330a68c519e43aea6e3f58/src/index.ts#L10

I think that problem is that cli is using tsc instead of a "proper" bundler like Parcel.

And Node does not support importing JSON files without an import assertion (require() works fine, though).

I tried this example:

import abc from './abc.json';

console.log(abc);

And indeed:

❯ node index.mjs
node:internal/errors:496
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///tmp/sdfsdfsd/abc.json" needs an import assertion of type "json"
    at new NodeError (node:internal/errors:405:5)
    at validateAssertions (node:internal/modules/esm/assert:94:15)
    at defaultLoad (node:internal/modules/esm/load:93:3)
    at DefaultModuleLoader.load (node:internal/modules/esm/loader:263:26)
    at DefaultModuleLoader.moduleProvider (node:internal/modules/esm/loader:179:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at #createModuleJob (node:internal/modules/esm/loader:203:17)
    at DefaultModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:156:34)
    at DefaultModuleLoader.getModuleJob (node:internal/modules/esm/loader:141:17)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:76:33) {
  code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
}

Node.js v20.5.1

Earlier versions (like Node 14) didn't support importing JSON files at all:

❯ nvm use 14
Now using node v14.21.2 (npm v6.14.17)
❯ node index.mjs
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".json" for /tmp/sdfsdfsd/abc.json
    at new NodeError (internal/errors.js:322:7)
    at Loader.defaultGetFormat [as _getFormat] (internal/modules/esm/get_format.js:71:15)
    at Loader.getFormat (internal/modules/esm/loader.js:105:42)
    at Loader.getModuleJob (internal/modules/esm/loader.js:243:31)
    at async ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:78:21)
    at async Promise.all (index 0)
    at async link (internal/modules/esm/module_job.js:83:9) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
baltpeter commented 1 week ago

The problem is the interaction between tsc and Parcel.

If I bundle the example from above using tsc (npx typescript index.ts --resolveJsonModule --esModuleInterop), it produces:

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var abc_json_1 = __importDefault(require("./abc.json"));
console.log(abc_json_1.default);

Meanwhile, Parcel keeps this as a JSON import:

import {version as $kYxtx$version} from "appstraction/package.json";
baltpeter commented 1 week ago

I had a similar problem in ReportHAR.

Unfortunately, just adding the import assertion didn't help—Parcel "helpfully" strips those in the output.

In ReportHAR, I was able to work around the problem by using Parcel's bundle inlining feature to… well, inline the JSON into the bundled code.

But I don't think that that is a valid solution here. That would mean that we essentially hardcode the appstraction version at build time. But that may not be the version that actually runs on the enduser's PC.

baltpeter commented 1 week ago

The problem is the interaction between tsc and Parcel.

Actually, that description doesn't quite describe the problem correctly. The reason that tsc produced CJS code in this example is that I ran it with a blank tsconfig.json and only supplied the resolveJsonModule and esModuleInterop via the command line.

If we repeat a similar experiment with cli's tsconfig.json (which is configured to output ESM: "target": "es2019"), it will also just preserve JSON imports:

import test from './test.json';

Running the output with node produces the same ERR_IMPORT_ASSERTION_TYPE_MISSING error.

Whether it preserves import assertions depends on the module option, which gets a lot more complicated. But I won't get into that, because all of this is beside the point, I was just correcting what I had said earlier.

I think the actual issue here is that the JSON import is in dependency. And tsc will just never touch those at all. So, the tsconfig.json settings and whatever tsc produces, don't even matter at all.

baltpeter commented 1 week ago

This is turning out surprisingly hard.

baltpeter commented 1 week ago

Oh, looks like someone has very kindly reimplemented the whole module resolution algorithm in a module: https://github.com/wooorm/import-meta-resolve!

While it's utterly ridiculous that we need something like that, this seems like the best solution to me.

The only other alternative that I had in mind was shipping a single .cjs file with require('appstraction/package.json').version).

baltpeter commented 1 week ago

That seems to work.

But of course, it won't work for getting our own version. So I guess, we will still need bundle inlining. sigh

baltpeter commented 1 week ago

Urgh, now bundle inlining isn't working for some reason. -.-

baltpeter commented 1 week ago

Welp, import-meta-resolve doesn't work either if I install CA as a dependency:

    Error: Cannot find package 'appstraction' imported from /src/util.ts
    Code: ERR_MODULE_NOT_FOUND

I'm wasting way too much time on this. CJS shim it is.

baltpeter commented 1 week ago

CJS shim doesn't work (trivially, at least) because Parcel helpfully folds that into the ESM module as:

var $e16ea7c30f77cffa$exports = {};
$e16ea7c30f77cffa$exports = {
    /**
     * @param {string} dependency
     *
     * @returns {Promise<string>}
     */ getDependencyVersion: async (dependency)=>{
        if (dependency === "cyanoacrylate") dependency = "..";
        return require(`${dependency}/package.json`).version;
    }
};

And that obviously fails with require is not defined.