FredKSchott / snowpack

ESM-powered frontend build tool. Instant, lightweight, unbundled development. ✌️
https://www.snowpack.dev
MIT License
19.48k stars 923 forks source link

[Plugins] Combine `build()` and `transform()` #542

Closed FredKSchott closed 4 years ago

FredKSchott commented 4 years ago

Right now these two plugin methods do essentially the same thing, but with the limitation that there can only ever be one build() per file type (and that build is able to transform file types).

We should combine these two into a single method (build(), transform(), or something else entirely) with the following qualities:

chengcyber commented 4 years ago

I found a way to support svg react component, which needs to extend the source code a little.

Adding the following code at https://github.com/pikapkg/snowpack/blob/master/src/commands/build-util.ts#L153 (neglecting a bit changes in function params syntax...)

for (const plugin of plugins) {
  const { wrapEsmProxyResponse } = plugin;
  if ("function" === typeof wrapEsmProxyResponse) {
    const customProxyResponse = wrapEsmProxyResponse({
      url,
      code,
      ext,
      hasHmr,
      config,
    });
    if (customProxyResponse) {
      return customProxyResponse;
    }
  }
}

which makes the plugin control proxy response.

thus, we can add a svg plugin maybe sth like this:

const { transformAsync, createConfigItem } = require('@babel/core');
const svgo = require('@svgr/plugin-svgo').default;
const jsx = require('@svgr/plugin-jsx').default;
const convert = require('@svgr/core').default;
const presetReact = require('@babel/preset-react');
const presetEnv = require('@babel/preset-env');
const pluginTransformReactConstantElements = require('@babel/plugin-transform-react-constant-elements');

const babelOptions = {
  babelrc: false,
  configFile: false,
  presets: [
    createConfigItem(presetReact, { type: 'preset' }),
    createConfigItem([presetEnv, { modules: false }], { type: 'preset' }),
  ],
  plugins: [createConfigItem(pluginTransformReactConstantElements)],
};

module.exports = function createPlugin(_, pluginOptions) {
  return {
    async transform({ urlPath, contents, isDev }) {
      // console.log(urlPath, contents, isDev);
      if (!urlPath.endsWith('.svg')) {
        return;
      }
      const code = await convert(contents, pluginOptions, {
        caller: {
          name: '@svgr/snowpack',
          defaultPlugins: [svgo, jsx],
        },
        urlPath,
      });

      const { code: result } = await transformAsync(code, babelOptions);

      return {
        result,
      };
    },

    wrapEsmProxyResponse({ url, code, ext, hasHmr, config: { buildOptions } }) {
      if (ext === '.svg') {
        return code;
      }
    },
  };
};

In this way, xxx.svg.proxy.js returns the react code for svg instead of export default '/path/to/svg.svg'

Hi @FredKSchott I have no idea whether this is the proper way to support svg react component. Any thoughts?

FredKSchott commented 4 years ago

Thanks @chengcyber! The work we just merged in #567 will allow for this via something like:

{
  input: [".svg"],
  output: [".js"],
  build() { /* run SVGR */ }
}

Or, if you only want to run this on a certain set of ".module.svg" files, you could use input: [".module.svg"].

This isn't quite supported yet, but the interface is now in place to support this in the near future.