rescript-lang / rescript-compiler

The compiler for ReScript.
https://rescript-lang.org
Other
6.74k stars 448 forks source link

Source-relative paths for require() statements? #1274

Closed quicksnap closed 1 year ago

quicksnap commented 7 years ago

Mentioned this in Discord this evening, figured I'd put it here instead.

I'm using bsb with webpack (via rehydrate-example), but I've added in css-loader/style-loader.

A common pattern with React+webpack is to bundle assets (css, images, json, etc) along with their component files and relative require them, having weback processes it: require('./image.jpg'). It results in a nice self-contained component that is easy to move around.

Reason code such as let styles: Js.t 'a = [%bs.raw "require('./styles.css')"]; doesn't work as webpack will be searching in the build output folder (lib/src/filename.js) instead of src.

Are there any existing solutions to make the source directory path available to BSB output?

quicksnap commented 7 years ago

Another thought is that I am using bs.raw since these require statements produce side-effects and shouldn't be optimized out.

glennsl commented 7 years ago

This is an issue we have with https://github.com/reasonml/reason-tools as well: https://github.com/reasonml/reason-tools/blob/master/src/extension/popup/PopupCommon.re#L7 (Also refmt is terrible at formatting %bs.raws, but that's another matter).

This is particularly annoying when refactoring and moving files around, it's very easy to lose track of these relative paths. But I think the real solution here might be better on the webpack side of things, maybe a custom loader?

quicksnap commented 7 years ago

Webpack can be given additional module resolve directories for files: https://webpack.js.org/configuration/resolve/#resolve-modules

However, webpack will have no way of knowing the mapping of the bsb-output.js file to its source file location.

Seems ripe for preprocessing to take care of it, and inject an absolute or relative path in replace of a token or something?

quicksnap commented 7 years ago

Actually, I'm going to review the Resolve and other webpack docs sometime tomorrow. There might be a way to fix this just with webpack and some config.

glennsl commented 7 years ago

Other than some syntax sugar, I don't think there's anything bsc/bsb can do that webpack shouldn't be able to do, and ought to do if possible.

yhsiang commented 7 years ago

Webpack 1.x First, use this config

module.exports = {
  ... 
  resolve: {
    modulesDirectories: [
      path.resolve(__dirname, "node_modules"),
      path.resolve(__dirname, "src")
    ]
  },
  module: {
    loaders: [
      { test: /\.css$/, loader: "style-loader!css-loader" }
    ]
  },
 ...
};

and write [%bs.raw {|require('app.css')|}]; in reason file.

I try to use fusebox but encounter the same problem, still finding the solution, maybe it will need to open issue to fusebox.

bobzhang commented 7 years ago

This maybe relevant to #792 What suggestions do you have in mind? Note that raw will never be optimized since we know nothing about it.

quicksnap commented 7 years ago

A few options come to mind:

bsb option to configure js output directory

If we allow the js output to be the same as src, then the output files will reside in the same directory as the source. AFAIK, this is how typescript projects work.

I do not like this approach personally, as it clutters my source directories with artifacts, but it does solve this class of problem.

webpack resolve.plugins

Solve it at the webpack layer. When we encounter a relative path, map the current path (lib/js/src/yaddayadda) back to source directory.

Drawback: the data we're seeking is encoded in the directory structure. If bsb alters how directory output works, this approach will break.

Some kind of ppx that replaces a token

I'm pretty naive even to how ppx syntax works, so this is psuedo-code:

let styles : Js.t 'a = [%bs.rawReplace "require("url-loader?mimetype=image/png!%%__DIRNAME%%./file.png");"];

Drawback: Ugly semantics. Probably brittle.

quicksnap commented 7 years ago

@yhsiang Re: modulesDirectories This is what I'm doing, but it doesn't allow for relative paths. So, When I move a component, I still need to update the paths. It's basically equivalent to what @glennsl is doing in reason-tools with long-ass relative paths.

yhsiang commented 7 years ago

modulesDirectories just setup a directory to module source, its original design is to prevent you use long relative paths. it makes webpack to find files from your module directory like node_modules or src.

src/app.css => require('app.css')
src/components/header/header.css => require('components/header/header.css')

I found fusebox allow you to use relative paths like this ~/../src/app.css(~ means homeDir which is setup for lib). First build will not work . But it works when you update some .re file include [%bs.raw 'require('~/..src/app.css')]. it will trigger many times update and seems bug.

bobzhang commented 7 years ago

we can have something like [%webpack.require "path/relative/to/package/xx.jpeg"], my only concern is that it would be abused.

would this work with webpack?

import * as styles from './xx.css'
quicksnap commented 7 years ago

@bobzhang that import statement would work.

It's worth remembering that webpack require statements can be also be in an inline loader format:

require('url-loader?mimetype=image/png!./rel/path/to/file.png')

These are useful in practice for one-off overrides of normal webpack config

quicksnap commented 7 years ago

Can PPX take multiple arguments? [%webpack.require "prefix info" "rel/path/file.png"]

bobzhang commented 7 years ago

@quicksnap It can, prefix-info works with es6 module? I would like to support webpack workflow, but we should mark it clearly since it is mostly a hack so that we can eventually get rid of it in the remote future. About the suffix, it is good to limit it to png, jpg, and css?

yhsiang commented 7 years ago

In webpack workflow, usually have these extensions, For fonts: woff, woff2, eot, ttf For images: svg, png, jpg For css: css, less, sass|scss

quicksnap commented 7 years ago

@bobzhang prefix-info would be optional, and is webpack-only syntax. Also, it's not used very much, in favor of webpack.config loader rules, so it'll usually be empty.

Feels quite hacky.. =\

bobzhang commented 7 years ago

@quicksnap yeah, it is very hackish, I am thinking of maybe you can keep these hacks in webpack land by using a plugin, for example

[%raw {|require ('__root_path___/path/to/file.png'|} ]

Suppose the webpack plugin could understand the meaning of __root_path__?

quicksnap commented 7 years ago

@bobzhang Main issue is that __root_path__ is relative to the source file. That data is structured in the bucklescript output directory structure.

Does bucklescript plan on changing the fact that output directory structure mirrors input structure? A webpack plugin is totally possible, but it depends on having the directory structure mapping.

bobzhang commented 7 years ago

@quicksnap Maybe I misunderstood, here webpack require is loading some resources (not output JS files), right? So as long as the resource does not change its relative location to package.json, then it is good, no?

assume

package.json
|---images
        |--- a.png

then

[%raw {| require("__root_path__/images/a.png")|}
quicksnap commented 7 years ago

@bobzhang For example, consider a normal Webpack/Babel/JS project:

package.json
|---src
     |---MyButton
               |--- MyButton.js
               |--- icon.png
// MyButton.js
let img = require('!url-loader?mimetype=image/png!./icon.png');

// OR, more normally:
// require('./icon.png'); 

// ... 

In this setup, I can relocate src/MyButton/** to somewhere else (src/components/MyButton) and not modify the require statement.

Using __root_path__ requires using the absolute directory structure of the project, e.g.

require(__ROOT_PATH__ + './src/components/MyButton/icon.png');

Does that clarify? A webpack plugin can accomplish this, but as mentioned, I would need to walk up the directory structure of lib/js/src and collect the directory names to connect back to the source dir.

bobzhang commented 7 years ago

@quicksnap yeah, I now understand, I kinda using the absolute directory structure is better, or maybe you can put all images in a single directory. It will get more complex here since we support multiple module formats, not just commonjs, but also es6, amdjs, google module, it would be lib/js/es6, lib/js/amdjs etc. If you have absolute path, it will work with all module format

alex35mil commented 7 years ago

Kinda related: https://github.com/rrdelaney/bs-loader/issues/4

daviddias commented 5 years ago

Thank you for outlining the options, @quicksnap. I feel you, having so many auto-generated files in my src folder is weird to me also. Subscribing to this issue waiting for a future elegant solution as well.

stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.