Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating dynamic aliases. #9588

Closed
tcmulder opened this issue Mar 19, 2024 · 11 comments
Closed

Creating dynamic aliases. #9588

tcmulder opened this issue Mar 19, 2024 · 11 comments

Comments

@tcmulder
Copy link

馃檵 feature request

Is it possible to create a dynamic alias? I'm using Parcel 2 for WordPress Gutenberg block development, so I need something that handles matching multiple imports to external resources. For example, imports for @wordpress/blocks and @wordpress/block-editor need to alias the global wp object as wp.blocks and wp.blockEditor.

馃 Expected Behavior

I'm hoping something like the following could work in the package.json file. It takes all @wordpress/something-kebab-case imports and converts them to the format wp.sometingCamelCase (or wp[somethingCamelCase]):

{
    "name": "wp-parcel",
    "version": "1.0.0",
    "description": "Parcel build tool for WordPress",
    "scripts": "...",
    "alias": {
        "@wordpress/*": {
            "global": "wp[$1.toLowerCase().replace(/(-\w)/g, m => m.toUpperCase().substr(1))]"
        }
    },
    "devDependencies": {
        "parcel": "^2.9.3"
    }
}

馃槸 Current Behavior

Currently, I need to list each and every import separately. The following example shows just shows two, and it does work in this format, but there are dozens more (and WordPress could add even more in the future), so it really bloats my package.json to cover each import separately like this:

{
    "name": "wp-parcel",
    "version": "1.0.0",
    "description": "Parcel build tool for WordPress",
    "scripts": "...",
    "alias": {
        "@wordpress/blocks": {
            "global": "wp.blocks"
        },
        "@wordpress/block-editor": {
            "global": "wp.blockEditor"
        },
        ...
    },
    "devDependencies": {
        "parcel": "^2.9.3"
    }
}

馃拋 Possible Solution

Glob aliases are similar to what I'm looking for, but they don't allow me to do some processing like transforming from kebab case to camel case, and even "global": "wp[$1]" chokes with a $1 undefined message.

This plugin may have worked for Parcel 1, but it looks like there are no immediate plans to make it work for Parcel 2. If Parcel 2 natively supported an external file that could handle the transformation like that older plugin does, it could be a good solution to reduce the size of the package.json.

馃敠 Context

I'm loving Parcel 2 for WordPress development and have been simply destructuring the global wp object instead of importing dependencies:

// rather than
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
// I instad do
const { InspectorControls, useBlockProps } = wp.blockEditor;

But, all WordPress examples and tutorials online follow the import syntax rather than destructuring, so as I'm sharing my custom theme with other developers I'm getting some raised eyebrows, and it's becoming a hassle to convert imports to destructured objects any time I copy code from the WordPress docs. If Parcel 2 supported dynamic aliases, it'd allow projects like mine to better match the coding standards of the platforms for which we're building.

@devongovett
Copy link
Member

Try writing a custom Resolver plugin: https://parceljs.org/plugin-system/resolver/

@tcmulder
Copy link
Author

Thanks @devongovett! I'm not very familiar with building my own resolvers: any chance you could point me in the right direction?

I created a basic resolver that simply logs the package and object names I want to match, then does nothing else:

import {Resolver} from '@parcel/plugin';

export default new Resolver({
  async resolve(data) {
    const {specifier} = data;
    if (specifier.startsWith('@wordpress')) {
		const propertyName = `${specifier}`.substring(11).toLowerCase().replace(/(-\w)/g, m => m.toUpperCase().substring(1));
		console.log({
			importNameIs: specifier,
			objectNameShouldBe: `wp[${propertyName}]`
		})
    }
    return null;
  }
});

I see examples at the link you provided for changing the filePath a package should use, but no pointers on ignoring the package altogether and using a globally-available object.property instead. Any help would be appreciated!

@mischnic
Copy link
Member

on ignoring the package altogether

https://parceljs.org/plugin-system/resolver/#excluding-modules

using a globally-available object.property instead

https://parceljs.org/plugin-system/resolver/#virtual-modules
with code being module.exports = object.property;

@tcmulder
Copy link
Author

Thanks @mischnic ! Possibly some progress. When I use the following, parcel watch no longer throws any errors in the console:

import {Resolver} from '@parcel/plugin';

export default new Resolver({
  async resolve(data) {
    const {specifier} = data;
    if (specifier.startsWith('@wordpress')) {
		const propertyName = `${specifier}`.substring(11).toLowerCase().replace(/(-\w)/g, m => m.toUpperCase().substring(1));
		return {
			isExcluded: true,
			code: `module.exports = wp[${propertyName}];`
		};
    }
    return null;
  }
});

However, in the browser I'm getting errors like "cannot find module '@wordpress/blocks'", and on parcel build I'm getting the error "external modules are not supported when building for browser."

Is isExcluded not taking effect for some reason?

@mischnic
Copy link
Member

mischnic commented Mar 22, 2024

Just remove the isExcluded: true. Then it should do what you want.

@tcmulder
Copy link
Author

tcmulder commented Mar 22, 2024

That did the trick: thanks so much for your help!

@tcmulder
Copy link
Author

Actually, that didn't work: turns out I still had my hard-coded aliases in the package.json file. Once I remove those, it's still erroring in the same manner as before. What can I try next?

@tcmulder tcmulder reopened this Mar 22, 2024
@mischnic
Copy link
Member

mischnic commented Mar 22, 2024

Turns out you do need to return a filepath as well. So that imports contained in the returned code can be resolved and so that Parcel knows the filetype of the returned code.

So you need

const { Resolver } = require("@parcel/plugin");
const path = require("path");

module.exports = new Resolver({
  async resolve({ options, specifier }) {
    console.log(specifier);
    if (specifier.startsWith("@wordpress")) {
      const propertyName = specifier
        .substring(11)
        .toLowerCase()
        .replace(/(-\w)/g, (m) => m.toUpperCase().substring(1));
      return {
        filePath: path.join(options.projectRoot, `wp-${propertyName}.js`), // this is arbitrary, ideally unique, has to have a JS extension
        code: `module.exports = wp[${propertyName}];`,
      };
    }
    return null;
  },
});

Parcel should throw an error in this case and tell you this

@tcmulder
Copy link
Author

Hmmm, still not working 馃檨

Screenshot 2024-03-22 at 10 50 21鈥疉M

@mischnic
Copy link
Member

Here's a full example that's working for me: https://github.com/mischnic/parcel-issue-9588

@tcmulder
Copy link
Author

Thanks so much, that did do the trick!

I needed to fix some code I had sent you to include quotes for the object property: it was resolving to wp[blockEditor] and throwing a runtime error of "undefined variable blockEditor", rather than resolving to wp['blockEditor']which is correct (i.e. the same aswp.blockEditor`). That didn't affect the build though, and is now fixed. Below is the code that worked for me in case it's helpful to someone else:

In the .parcelrc file:

{
  "extends": [
    "@parcel/config-default"
  ],
  "resolvers": [
    ".parcel-resolvers.mjs",
    "..."
  ]
}

In the .parcel-resolvers.mjs file (or whatever you choose to name it):

import {Resolver} from '@parcel/plugin';
import path from 'path';

export default new Resolver({
  async resolve({ options, specifier }) {
    if (specifier.startsWith('@wordpress') && ! ['@wordpress/icons'].includes(specifier)) {
      const propertyName = specifier
        .substring(11)
        .toLowerCase()
        .replace(/(-\w)/g, (m) => m.toUpperCase().substring(1));
      return {
        filePath: path.join(
          options.projectRoot,
          `wp-${propertyName}.js`
        ),
        code: `module.exports = wp['${propertyName}'];`,
      };
    }
    return null;
  },
});

Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants