vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.91k stars 26.97k forks source link

Add support to transpile modules inside node_modules #706

Closed arunoda closed 2 years ago

arunoda commented 7 years ago

Now some of us ships NPM packages (specially components) written in ES2015 without transpiling them.

That's a pretty good thing specially if they are gonna used in a project like Next.js or CRA (which does transpiling). They offer benefits like:

But we can't do this now we exclude everything inside node_modules from babel transpiling.

So, here's the proposed solution.

We have an entry in next.config.js to include modules which needs to go through babel. See:

module.exports = {
   transpileModules: [
     "my-component",
     "redux/src"
   ]
}
rauchg commented 7 years ago
arunoda commented 7 years ago

Why is this not doable with webpack() extension?

I hope you mean custom webpack config. That's doable. But this sits in a existing loader. Getting that is somewhat harder.

transpileModules sounds better

Awesome. I'll update.

Will we accept regexps? Yep. sure.

slorber commented 7 years ago

Hey,

As I'm starting my own website I'm trying new techs like Lerna/Next/Styled... and would be happy to provide early feedback on this.

I've opened a duplicate issue where I tried to import/transpile a CRA-based module in my NextJs module but didn't know how to make the transpilation happen (note that I'd like to keep my module runnable as a standalone)

I've also noticed that Babel, also based on Lerna, is transpiling each modules before exposing them, but it looks to me better to do like @arunoda suggest and let the client app do the transpiling.

I'd like to have a single babel config for my client and share that config with all my decoupled modules. That's probably not so easy if I want to keep ability to run my modules as standalone, outside of Next runner

My current test project is here: https://github.com/slorber/playground/ I'll try to upgrade it as soon as there's a fork/PR. @arunoda are you working on it?

arunoda commented 7 years ago

@slorber currently we are focusing on 2.0 release and we are fine tuning stuff and finding bugs as possible as we can.

I haven't started work on this but we can do this just after 2.0.

slorber commented 7 years ago

Ok so I'll make a fork. I'm already running against 2.0.0 beta because I'm not building a critical website and I don't think webpack 1.13 resolve jsnext:main/module field.

I'm not a bundler expert but I think I'd rather use "module" field of package.json no? "main" seems for already transpiled code as far as I know. But as the webpack config allows to include/exclude transpilation I'm not sure it's relevant. Any recommendation on which of the 3 fields I'd rather use?

arunoda commented 7 years ago

@slorber I think webpack only supports main just like NPM. You can use that. We can check for the filepath in the exclude function in our next.conf.js

slorber commented 7 years ago

Hmm according to what I've seen in practice against Next 2.x I've seen module works (but fails later at runtime because not transpiled) while jsnext:main did not work (as far as I remember). But it's supposed to be supported.

Anyway, jsnext:main or module does not seem to be the solution to this problem so for company-internal modules just enabling transpilation is probably enough

rauchg commented 7 years ago

The community has not agreed on one approach right? For example, I was able to use react-youtube the other out of the box with no problems. I'm assuming a big number of modules transpile before publish?

Ref: https://github.com/rauchg/blog/blob/master/components/post/youtube.js

slorber commented 7 years ago

Yes that makes sense to always transpile before publish because you don't know who/how the module will be consumed and you don't want to force the client to setup appropriate babel settings for your lib. That's what Rollup suggest: to publish the module transpiled in different ways so that bundler can decide which to use.

But for company internal packages, the transpilation settings might be the same across several project (like a babel preset) and it makes sense to me to let the client bundler to transpile all the company dependencies

philcockfield commented 7 years ago

Very much agree @slorber - this would be very handy for internal modules if you're breaking your project up and isolating things as much as possible.

And @rauchg / @arunoda supporting RegExp's would be really nice, so you could have one entry that catches all the company internal modules, using say the NPM org namespace:

// next.config.js
module.exports = {
  transpileModules: [
    /^\@my-npm-org\/.*/
  ]
}
rauchg commented 7 years ago

Beautiful suggestion @philcockfield

slorber commented 7 years ago

Hey maybe it could be worth offering some presets. It looks to me most tools (Lerna/npm link...) rely on symlinks so why not something as simple as:

module.exports = {
  transpileModules: ["symlinks"]
}
philcockfield commented 7 years ago

The more I use next.js in earnest, and build out a rich library of modules around it, the more this feature becomes important. It's becoming a real PITA replicating the babel compilation step in my internal modules.

🚀🤖

arunoda commented 7 years ago

I'm working on this today :)

arunoda commented 7 years ago

@philcockfield give this a try: https://github.com/zeit/next.js/pull/749

slorber commented 7 years ago

thanks @arunoda

So as commented on your PR if this does not support symlinks the feature will be a bit limited because it won't work with npm link or Lerna, but only for npm modules that are not transpiled (right? I don't see any other usecase unless you commit modules inside /node_modules)

Why not supporting symlinks? is it harder to support?

Also I wanted to test your branch on my app, but I'm not sure what's the best way to do that. Is there any known procedure so we can easily test a branch and it's not too painful for the tester? I've tried some stuff like:

What's the best way to test a fork currently?

lorensr commented 7 years ago

If you're looking at doing this with next.config.js: module.exports = { webpack: (config, then config.module.rules has a few things, looks like you need to change one of these rules, or add one?:

  { loader: 'babel-loader',
    include: '/Users/me/gh/guide/node_modules/next/dist/pages',
    options: 
     { babelrc: false,
       cacheDirectory: true,
       sourceMaps: 'both',
       plugins: [Object] } },
  { test: /\.js(\?[^?]*)?$/,
    loader: 'babel-loader',
    include: 
     [ '/Users/me/gh/guide',
       '/Users/me/gh/guide/node_modules/next/dist/pages' ],
    exclude: [Function: exclude],
    query: 
     { babelrc: true,
       cacheDirectory: true,
       sourceMaps: 'both',
       presets: [] } } ]

Looking forward to the simpler syntax suggested.

andrewmclagan commented 7 years ago

Sorry for my ignorance I cant see what the resolution of this issue is? We would love to be importing es6 into our codebase, we need the tree-shaking .

Is there a PR on this?

slorber commented 7 years ago

@andrewmclagan This issue is still open and has a related PR that probably won't satisfy all (like LernaJS users)

Yuripetusko commented 7 years ago

What's the status of this? Are there any other ways to make next's webpack to transpile files imported from node_modules ?

andrewmclagan commented 7 years ago

@slorber i will take a look at the PR. Contribute our use-case.

muhajirdev commented 7 years ago

I am facing kind of similiar problem. Trying to use get-urls package. Works find with dev but when i compile it. I got error from uglify

...
{ Error: commons.js from UglifyJs
...

Is there any workaround for this please?

timneutkens commented 7 years ago

Arunoda will work on it sometime here. He has done before in #749

eliot-akira commented 7 years ago

I'd love to see this feature implemented. As @philcockfield mentioned, it's a common scenario to build up a library of modules that depend on Next.js transpilation, and it'd be great to be able to share components among projects.

ernestofreyreg commented 7 years ago

This is not only needed for tree shaking. Also for babel plugins like styled-jsx. So if you have a module (like a library) that uses a babel plugin the best solution is to include the ES6 source code and allow your app to transpile it from node_modules. Of course next already includes styled-jsx by default.

thealjey commented 7 years ago

Here's what I did

// next.config.js
exports.webpack = config => (config.module.rules = config.module.rules.map(({exclude, ...rest}) => ({
  exclude: Object.prototype.toString.call(exclude) === '[object Function]' ? (str => !/mycomponents/.test(str) && exclude(str)) : exclude,
  ...rest
})), config);

I basically replaced each exclude with a custom function. I do not know what I am doing wrong, but I just cannot make it work. I need the contents of node_modules/mycomponents to also be transpiled by Next.js

It doesn't even work if I completely override all excludes with an empty array

exports.webpack = config => (config.module.rules = config.module.rules.map(({exclude, ...rest}) => ({
  exclude: [],
  ...rest
})), config);

Please help me :) Thanks

damianobarbati commented 7 years ago

Hey guys ( @thealjey ) I've been transpiling jsnext:main powered modules for months by now.

I'm not using next.js but I hope it helps.

thealjey commented 7 years ago

@damianobarbati no, unfortunately it does not Configuring webpack directly to transpile whatever is not difficult, but I am struggling to make this work in the context of Next.js

revolunet commented 7 years ago

hey all, anyone have found a solution?

i have a local ES6 linked node module i need to import in my project but i cant get the webpack voodoo right !

gwintrob commented 7 years ago

I'm sure there's a better way, but we run everything through babel on build:

next build && babel .next/*.js --out-dir . --presets=es2015,react

mattfelten commented 7 years ago

Did this die? I'm looking for a way to transpile a custom module and seems like it's still not possible.

timneutkens commented 7 years ago

@mattfelten it's on the roadmap for v5 👍

makis-spy commented 7 years ago

Does anyone have an example of a workaround for this?

chrisui commented 6 years ago

@timneutkens Is there any timeline for this? Appreciate that's often an impossible question but we're trying to determine our stack at work as of now and this is a pretty big blocker for us! :)

Workaround suggestions also valid.

chrisui commented 6 years ago

@thealjey realise this is an old comment but your solution probably didn't work because there is an include specified as well which would need to be overridden.

Update: looked into this strategy but it's just not sane given all the different loaders for different module directories within next.js' internal configuration. This will need to be first-class.

thealjey commented 6 years ago

@chrisui my (temporary) solution was to use babel-plugin-module-resolver, configured like this - "plugins": [["module-resolver", {"root": ["./"]}]] it is by no means a perfect solution, but, since everything else failed, it works for now thanks to that I do not need to write a bunch of ../ with every import this might actually be a better solution to some, though it certainly doesn't help with reusability

jamesgorrie commented 6 years ago

@thealjey could you give an example? I have a project setup where I try this to no avail... https://github.com/jamesgorrie/nappy

I'd happily try get a PR in as this would make our life so much easier, but there are a few questions like: Should next.js support transpiling of certain modules or should this be up to the transpiler, but the next.js follows the module resolution more strictly. Not sure who to ask or where to start here as new to next.js.

oliverjam commented 6 years ago

it's on the roadmap for v5 👍

@timneutkens did this make it into v5?

blackbing commented 6 years ago

wondering to know.

andrewmclagan commented 6 years ago

Just look at where its merged. this was merged 8 days ago. 5.0.0 was released 2 days ago. from the canary branch where this is merged into...

brianyingling commented 6 years ago

Does anyone have an example on how this can be implemented? Is it supposed to work like this, as mentioned above?

module.exports = {
    transpileModules: ['my-npm-module']
}

or does it look different?

timneutkens commented 6 years ago

https://github.com/zeit/next.js/pull/3732

brianyingling commented 6 years ago

I'm confused. @timneutkens The abovementioned link is to a PR that is still open. Has this NOT been merged in v5 yet?

EDIT: Never mind, this is just a PR to highlight the example.

timneutkens commented 6 years ago

It's an example, you can use the example without it being merged. transpileModules is something we'll tackle later on.

As a general rule of thumb: When an issue is open it's not released.

jamesgorrie commented 6 years ago

@brianyingling I've turned the example into a plugin for an interim solution (v5 only). It's working for us at the moment, until the more robust, official solution is put in place.

https://www.npmjs.com/package/@weco/next-plugin-transpile-modules

statico commented 6 years ago

I would love to see an example of this for a single module in the application codebase.

For example, I have a module that I use both server and client side. I haven't been able to get any of the above examples to work, either by factoring the module into its own separate module and yarn linking it, or by hacking the webpack rules.

Here's a simple repro: https://github.com/statico/nextjs-with-async-lib

// pages/index.js
import { foo } from '../lib/test'
export default () => <div>hello {String(typeof foo)}</div>

// lib/test.js
async function foo () {}
module.exports = { foo }

@timneutkens tells me this is because I'm using async/await in the shared module. I guess my solution might be to remove async/await and change everything to .then()-style callbacks.

statico commented 6 years ago

OK, I found a fix that works for me.

First, I added the config.resolve.symlinks = false setting to my next.config.js configuration per https://github.com/zeit/next.js/issues/3018#issuecomment-380879576

// next.config.js
webpack: (config, { dev }) => {
  config.resolve.symlinks = false
  return config
}

Then I put my shared library — a .js file that uses CommonJS exports and async/await keywords — in a subdir of the application called shared:

// shared/index.js
async function foo () {}
module.exports = { foo }
// shared/package.json
{
  "name": "@myapp/shared",
  "version": "1.0.0",
  "main": "index.js",
  "license": "UNLICENSED",
  "private": true,
  "dependencies": { ... }
}

And finally I added a postinstall script to link it all together when anyone does a yarn install in the main application:

// package.json
{
  ...
  "scripts": {
    "postinstall": "cd shared ; yarn -s unlink ; yarn link && yarn && cd .. && yarn link @myapp/shared",
   ...

Now my Mocha tests pass on the server side, my custom Koa server starts up fine, and there's no more crazy Cannot assign to read only property 'exports' of object '#<Object>' in my NextJS pages.

stugoo commented 6 years ago

I had this same issue when upgrading to NextJs 5.1.0. One or two of the node modules in next were not transpiling fat arrow functions and throwing errors in IE11. I had previously been through setting up individual polyfills and in the end I opted to target those modules files with babel-polyfill in my next.config.js with this:

module.exports = {
  webpack: (config, { dev }) => {
    const polyfill = new Promise((resolve, reject) => {
      const originalEntry = config.entry

      originalEntry().then(entries => {
        if (entries['main.js']) {
          entries['main.js'].unshift('./client/polyfills.js')
          entries['main.js'].unshift('babel-polyfill')
        }
        config.entry = entries

        resolve()
      })
    })

    config.module.rules.push(
     {
        test: path.resolve('./node_modules/next/node_modules/'),
        loader: 'babel-loader',
        options: {
          babelrc: false,
          cacheDirectory: false,
          presets: ['es2015']  
        }
      }
    )

    return polyfill.then(() => { return config })
  }
}

Hope this helps someone.

curran commented 6 years ago

ESM works like a charm.

With a custom Next.js server in index.js, I can run this command to start the server and esm kicks in perfectly, resolving ES modules in Lerna-symlinked project packages.

node -r esm index.js
blackbing commented 6 years ago

@curran cool, can it replace babel-node ?