vercel / ncc

Compile a Node.js project into a single file. Supports TypeScript, binary addons, dynamic requires.
https://npmjs.com/@vercel/ncc
MIT License
9.27k stars 290 forks source link

Behaviour of ncc'd libraries requiring code dynamically #479

Open OlliV opened 5 years ago

OlliV commented 5 years ago

It seems that by default ncc (or webpack) isn't configured to preserve exports. Is it possible to do that?

TooTallNate commented 5 years ago

Can you elaborate on what you mean? All of our main builders are indeed compiled via ncc, and the exports remain intact.

Also, this simple example preserves the exports:

// t.js
exports.foo = 'bar';
$ ncc build t.js
1kB  dist/index.js
1kB  [285ms] - ncc 0.16.0
> require('./dist')
{ foo: 'bar' }
OlliV commented 5 years ago

Interesting. I was using TS and all I got was this:

console.log(require('...'));
{ }

I need to try to find the min reproduction of the issue. I was using several exports and a default export. Once that didn't work I also tried to reproduce TS-like exports in the node.js way but I was still getting an empty object.

OlliV commented 5 years ago

Ok the issue seems to be related to npm link and dynamic requires.

Let's say you have project-1 that is built with ncc and there is a function in project-1 that can dynamically require and execute code from any path given as an arg.

Then project-2 uses npm link project-1 and calls that function passing a path of file in its own source tree. So far everything works and the code is loaded and can be executed by project-1. However, if the file from project-2 that is being executed by project-1 now also tries to require something from project-1 only in this case will require('project-1') return an empty object.

OlliV commented 5 years ago

I have a very interesting repro without npm link. It seems like dynamic require works fine if the code build with ncc is not in the node_modules directory.

https://files-8mwa86l42.now.sh

ncc-issue-repro/
ncc-issue-repro/package.json
ncc-issue-repro/yarn.lock
ncc-issue-repro/index.js
ncc-issue-repro/node_modules/
ncc-issue-repro/node_modules/.bin/
ncc-issue-repro/node_modules/.bin/micri
ncc-issue-repro/node_modules/.yarn-integrity
ncc-issue-repro/node_modules/test/
ncc-issue-repro/node_modules/test/package.json
ncc-issue-repro/node_modules/test/yarn.lock
ncc-issue-repro/node_modules/test/tsconfig.json
ncc-issue-repro/node_modules/test/src/
ncc-issue-repro/node_modules/test/src/index.ts
ncc-issue-repro/node_modules/test/dist/
ncc-issue-repro/node_modules/test/dist/index.d.ts
ncc-issue-repro/node_modules/test/dist/index.js
ncc-issue-repro/test/
ncc-issue-repro/test/package.json
ncc-issue-repro/test/yarn.lock
ncc-issue-repro/test/tsconfig.json
ncc-issue-repro/test/src/
ncc-issue-repro/test/src/index.ts
ncc-issue-repro/test/dist/
ncc-issue-repro/test/dist/index.js
ncc-issue-repro/test/dist/index.d.ts
ncc-issue-repro/run.sh

I'm running here first the index.js file that just requires test from both the direct subdir as well as using the standard module require string format, and that works fine. 'here' is a string exported by the module compiled with ncc.

Now things get really interesting if I execute the module compiled with ncc and require index.js dynamically from there. As you can see from the logs the second case, which should be the normal use case, now sees undefined.

 ./run.sh
+ node index.js
Path: /home/hbp/tmp/ncc-issue-repro/index.js
node_modules: here
Path: /home/hbp/tmp/ncc-issue-repro/index.js
local here
+ node ./node_modules/test/dist/index.js
Path: /home/hbp/tmp/ncc-issue-repro/index.js
node_modules: undefined
Path: /home/hbp/tmp/ncc-issue-repro/index.js
local here
OlliV commented 5 years ago

Here is the code so you don't necessarily need to open the tar file:

index.js

const {findMe: a} = require('test');
console.log('node_modules:', a);

const {findMe: b} = require('./test');
console.log('local', b);

test/index.js

const {findMe: a} = require('test');
console.log('node_modules:', a);

const {findMe: b} = require('./test');
console.log('local', b);
[hbp@hbp-mac-fedora]~/tmp/ncc-issue-repro% cat test/dist/index.js
module.exports=function(e,r){"use strict";var t={};function __webpack_require__(r){if(t[r]){return t[r].exports}var u=t[r]={i:r,l:false,exports:{}};e[r].call(u.exports,u,u.exports,__webpack_require__);u.l=true;return u.exports}__webpack_require__.ab=__dirname+"/";function startup(){return __webpack_require__(325)}return startup()}({325:function(e,r,t){"use strict";var u=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(r,"__esModule",{value:true});const n=u(t(622));r.findMe="here";function fn(){const e=n.default.resolve(process.cwd(),"index.js");console.log(`Path: ${e}`);require(e)}fn()},622:function(e){e.exports=require("path")}});

test/index.ts

import path from 'path';

export const findMe = 'here';

function fn() {
    const filePath = path.resolve(process.cwd(), 'index.js');
    console.log(`Path: ${filePath}`);
    require(filePath);
}

fn();
OlliV commented 5 years ago

Further on it seems this occurs if I require the same file that executed my code. If I use a physically copy of index.js that contains findMe then it's fine. Symlink doesn't work. The copy of the file can be exact copy but it must be located in a different path and can't be linked.

bradennapier commented 4 years ago

Yeah I think I may be running into this when trying to use the eslint cli packaged in for a github action?