parcel-bundler/parcel

Add an aliasing system that works for all asset types

Closed this issue ยท 12 comments

Is there a way to define alias ? Like webpack config:

2017-12-06 10 16 24

So I take it that your usecase is to avoid typing long relative paths everywhere? In general I'm not a huge fan of doing this as it makes it much harder to reason about where the code you read in a project is coming from. I'd rather stick with the standard Node module resolution algorithm and avoid having each project define its own strategy.

If we did something here, I'd want the aliases to be scoped to a particular module. For example, the aliases in the top-level project shouldn't apply within node_modules, and aliases defined within node_modules shouldn't apply to other node_modules.

I'm open to proposals for how to implement this, but I can't guarantee that it's something I'll want to support. For JS, the above babel plugin might work OK for now.

This can't be completely pushed onto Babel since it needs to work across transforms (html, css, etc).

I'm also not a fan of doing this for long paths, but it is important for mapping things like "react" to "preact". In which case you do want it to operate across module boundaries.

I think we might be able to scope this to packages, but for cases like Preact you'd have one of three options:

  1. Aliases in package.json and node_modules/.../package.json apply to all dependencies - this seems like the best option to me but de-duping could prevent it from working.
  2. Aliases in package.json apply to all dependencies, but aliases in node_modules/.../package.json only applies to it's own source - this seems like the next best option
  3. Special configuration for applying an alias globally - this seems like the easiest to explain.

it'd have to include their children for those cases like Preact.

I can imagine something inside of package.json like:

{
   "name": "my-project",
   "alias": {
     "react": "preact"
   }
}

I started by just attempting to do a "quick switch" from webpack to parcel to see how easy it would be and what results would be produced on a medium to large sized project. The alias issue was the first hurdle. As I started resolving those issues I came across a number of individual gotchas and bumps.

  1. When processing files, if you accidentally have an empty sass file in one of your directories you will get a error that looks something like this:
    .../styles.scss:undefined:undefined: No input specified: provide a file name or a source string to process

Solution: Delete empty file

  1. Throughout resolving the aliases I had to continuously restart parcel because I was getting errors that looked like this:
    Cannot read property 'js' of null at Bundler.createBundleTree (/Users/username/.config/yarn/global/node_modules/parcel-bundler/lib/Bundler.js:434:24)

Solution: Restart parcel

  1. Once the aliases were resolved I started getting errors about libraries I included:
    /Users/username/.../node_modules/react-table/lib/index.js: Couldn't find preset "stage-2" relative to directory "/Users/username/.../node_modules/react-table"

Solution: npm install --save-dev babel-preset-stage-2 and add to .babelrc presets: [ "stage-2" ]

chee commented

As a note, I am very into what I think is unpopular idea, which is to allow setting the effective root of a project. Sort of like a chroot, so you could do:

# somewhere.json
{
  "root": "./src"
}
// file.js
import dogs from '/lists/dogs'

and that would get it from ./src/lists/dogs.

This gets around the problem of code that can stop working when a module is installed with the common NODE_PATH, babel-plugin-module-resolver and webpack approach1.

I think it also gets around the problem of reasoning about where the code you are reading is coming from. It means that a user who has configured this can no longer require a file in their project using an absolute path. I've never seen this done, and it seems a worthwhile trade-off.

Footnote By this i mean, if you've configured `plugins.module-resolver.root` to `'./src'`, and you have
import Dog from "components/dog"

and then you npm install a package that depends on a package called components, your Dog stops working!

@devongovett the main point it's a way to refer to "srcDir" and "rootDir" like in nuxt, of course it's a bad pattern creating an alias for every folder, but it's awful referencing files using ../../xxx.js.

You can just enforce a standard like nuxt or allow alias, but for a "zero config", the idea of just the default: ~: srcDir and ~~: rootDir, when srcDir is basically a src/lib/app folder if it exists else srcDir reference rootDir, perhaps use ~ as rootDir, like in linux, when we use ~ as home folder and @ as srcDir

@chee idea as a problem, if a package it's on node_modules and on root what exacly happens?

Yeah the react -> preact case is pretty compelling. definitely need to support that.

In fact, parcel already supports a super basic version of that in the form of the browser field in package.json, which is also supported by browserify https://github.com/defunctzombie/package-browser-field-spec. However, this does only apply to the current package (e.g. not outside the current node_module).

Can someone write up a proposal for how this should work in detail? Basically, I think it should work the same way as browser but apply globally, and only for the root package.json (e.g. not inside node_modules). I think if any package in node_modules could affect aliases globally, there would be chaos. This is basically @thejameskyle's option #2.

Whether alias should work at all for node_modules or only the root package is still up for debate I think. If we want it to work, then it should work identically to browser (i.e. only apply within the current package). If not, then there is always browser which is already supported across bundlers.

I've tried using babel-plugin-module-resolver, but it seems to have no effect on Parcel. It works with webpack, but the same config does not work with Parcel, nor any kind of config permutation I could think of.

babelrc plugins config that works with webpack:

  [
          "module-resolver",
          {
            "alias": {
              "ps": "./src"
            }
          }
        ]
      ]

Error in parcel

kelly@interconcernedcat:~/src/product-store/src (km/babel-alias *)$ parcel app.js
โณ  Building...
Server running at http://localhost:1234
๐Ÿšจ  /Users/kelly/src/product-store/src/app.js:6:18: Cannot resolve dependency 'ps/store'
  4 | import { IntlProvider } from 'react-intl'
  5 | import routes from 'ps/routes'
> 6 | import store from 'ps/store'
    |                   ^
  7 | import { getLocale, messages } from 'ps/lib/locale'
  8 |
  9 | export default class App extends Component {

It would be useful to know where parcel is looking when resolving them module, webpack outputs this for debugging. Looks like it uses enhanced-resolve which outputs the error.

Example webpack resolution error:

    at resolver.doResolve.createInnerCallback (/Users/kelly/src/product-store/node_modules/enhanced-resolve/lib/DescriptionFilePlugin.js:44:6)
resolve '../routes' in '/Users/kelly/src/product-store/src'
  using description file: /Users/kelly/src/product-store/package.json (relative path: ./src)
    Field 'browser' doesn't contain a valid alias configuration
  after using description file: /Users/kelly/src/product-store/package.json (relative path: ./src)
    using description file: /Users/kelly/src/product-store/package.json (relative path: ./routes)
      as directory
        /Users/kelly/src/product-store/routes doesn't exist
      no extension
        Field 'browser' doesn't contain a valid alias configuration
        /Users/kelly/src/product-store/routes doesn't exist
      .js
        Field 'browser' doesn't contain a valid alias configuration
        /Users/kelly/src/product-store/routes.js doesn't exist
      .json
        Field 'browser' doesn't contain a valid alias configuration
        /Users/kelly/src/product-store/routes.json doesn't exist
error Command failed with exit code 1.

Basically, I think it should work the same way as browser but apply globally, and only for the root package.json (e.g. not inside node_modules). I think if any package in node_modules could affect aliases globally, there would be chaos.

Why not leave it just as package.json#browser for local stuff, and have an aliases field which is only applied from the root?

from @shawwn, cc @sompylasar

Yep, resolve aliases are WIP. I have the heavy lifting done locally. All
the FS calls are trapped in a central filesystem object stored in Parser,
and you can even set Parcel to output files to an instance of memory-fs
to spit out files in memory instead of save them to disk.

Suffice to say, require("~foo/bar") won't be a problem.

Still undecided whether to use ~ or just stick with / as the relative
root... Also, how to specify where the relative root should begin? What if
parcel's working directory is something other than project root? We can't
walk upwards looking for package.json, nor can we assume a .git folder
marks the top of the project.

I'm going to lock this because this issue is to discuss adding aliases to Parcel, not for people to figure out how to get it done today

See #850 for an implementation of aliases as discussed above. Please comment there if you have any feedback.