DEV Community

Cover image for Adding URL Search Parameters to Imports!
Lioness100
Lioness100

Posted on

Adding URL Search Parameters to Imports!

Here's an interesting bit of knowledge: did you know that you can create and interpret relative file URLs like... actual URLs?

All content covered in this post will only work with ESM

Now, it might be hard to find a practical use for this, but it is undoubtedly very cool 😎. Enough talk; let's look at some code.

// In your first file:
import './example.js?abc=123';
Enter fullscreen mode Exit fullscreen mode
// In your second file, example.js:
const url = new URL(import.meta.url);
const searchParam = url.searchParams.get('abc');

console.log(searchParam); // β†’ '123'
Enter fullscreen mode Exit fullscreen mode

Here, import.meta.url is used to display the full URL used to import the module. In this case, for example, it might be:

file:///C:/Some/Folder/Path/example.js?abc=123
Enter fullscreen mode Exit fullscreen mode

From here, we can create an instance of a URL, which will make it super easy to parse search parameters.

How could this be used?

I have no idea πŸ˜‚! Maybe it could be used as a shortcut for very primitive module configuration? For example, using the very popular utility package dotenv.

GitHub logo motdotla / dotenv

Loads environment variables from .env for nodejs projects.

Works with dotenv-vault. Learn more at dotenv.org.

dotenv

dotenv

Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env. Storing configuration in the environment separate from code is based on The Twelve-Factor App methodology.

BuildStatus Build status NPM version js-standard-style Coverage Status LICENSE Conventional Commits Rate on Openbase

Install

# install locally (recommended)
npm install dotenv --save
Enter fullscreen mode Exit fullscreen mode

Or installing with yarn? yarn add dotenv

Usage

Create a .env file in the root of your project:

S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"
Enter fullscreen mode Exit fullscreen mode

As early as possible in your application, import and configure dotenv:

require('dotenv').config()
console.log(process.env) // remove this after you've confirmed it working
Enter fullscreen mode Exit fullscreen mode

.. or using ES6?

import 'dotenv/config' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import express from 'express'
Enter fullscreen mode Exit fullscreen mode

That's it. process.env now has the keys and values you defined in your .env file:

require('dotenv').config()
.
…
Enter fullscreen mode Exit fullscreen mode

Instead of:

import dotenv from 'dotenv';
dotenv.config({ debug: true });
Enter fullscreen mode Exit fullscreen mode

You could do this! πŸ¦„

import 'dotenv/config?debug=true';
Enter fullscreen mode Exit fullscreen mode

I'd love to know your thoughts πŸ˜†

Top comments (6)

Collapse
 
servernoj profile image
servernoj • Edited

Does anybody know how to use this trick with Typescript? Say, I have a TS project with "type": "module" in package.json and "module" set to "esnext" in tsconfig.json. Then, I have 2 TS files in the same directory, e.g. a.ts and b.ts with a line in b.ts reading:

import {...} from './a.js'
Enter fullscreen mode Exit fullscreen mode

and it works fine after compilation with tsc (please notice the use of a.js instead of a in the import statrement -- this is a requirement for imports of local ESM modules)

But... when I replace import statement to

import {...} from './a.js?q=hello'
Enter fullscreen mode Exit fullscreen mode

then tsc throws error that it cannot find module.

The only solution that I know is to suppress TS error checking by adding @ts-ignore like that:

// @ts-ignore
import {...} from './a.js?q=hello'
Enter fullscreen mode Exit fullscreen mode
Collapse
 
gabrielrurbina profile image
Gabriel Urbina

There is limited support for this, add this into your tsconfig.json,

{
    "compilerOptions": {
        "paths": {
            "./somedir/*?q=hello": [
                "./somedir/*"
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Have a look at path-mapping

Collapse
 
abc_wendsss profile image
Wendy Wong

Great article by Lioness100 and thanks @servernoj for asking a thoughtful question and sharing with the DEV community πŸ™‚

Collapse
 
kopseng profile image
Carl-Erik Kopseng

Here's an actual usage for you: cache busting the browser's cached resolution of module imports after it has failed. You see, browsers cache the initial loading of a module, including the failure state, so that the runtime does not have to download the same module multiple times. Unfortunately, that means an intermittent network error in your SPA will make it fail consistently.

That's why I published this library to catch those errors and retry by appending a cache busting query parameter to the module path.

Collapse
 
prantlf profile image
Ferdinand Prantl • Edited

I use it in my test harness to create a named test suite by a single import line. Instead of the usual importing index.min.mjs and calling the test suite factory:

import tehanu from './node_modules/tehanu/dist/index.min.mjs'

const test = tehanu('sum')
Enter fullscreen mode Exit fullscreen mode

I can import suite.min.mjs which recognises the URL parameter name, creates a test suite with the name and exports it:

import test from './node_modules/tehanu/dist/suite.min.mjs?name=sum'
Enter fullscreen mode Exit fullscreen mode

It's a pity that this works only in browsers. Node.js doesn't separate the URL parameters from the file path, which leads to an invalid script path, if they are attached:

❯ node 'lib/index.mjs'
no suites

❯ node 'lib/index.mjs?test'
node:internal/modules/cjs/loader:936
  throw err;
  ^
Error: Cannot find module '/.../tehanu/packages/teru/lib/index.mjs?test'
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
    at Function.Module._load (node:internal/modules/cjs/loader:778:27)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
Enter fullscreen mode Exit fullscreen mode

This has probably a consequence that the URL parameters can't be used in subpackages either. If a library exposes a subpackage suite, for example:

{
  "exports": {
    ".": {
      "require": "./lib/index.cjs",
      "import": "./lib/index.mjs"
    },
    "./suite": "./lib/suite.mjs"
  }
}
Enter fullscreen mode Exit fullscreen mode

it has to be used as-is:

import test from 'tehanu/suite'
Enter fullscreen mode Exit fullscreen mode

instead of the desired:

import test from 'tehanu/suite?name=sum'
Enter fullscreen mode Exit fullscreen mode

which fails:

❯ node lib/index.mjs test/index.mjs
node:internal/errors:465
    ErrorCaptureStackTrace(err);
    ^
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './suite?name=sum' is not defined by "exports" in /.../tehanu/packages/teru/node_modules/tehanu/package.json imported from /.../tehanu/packages/teru/test/index.mjs
    at new NodeError (node:internal/errors:372:5)
    at throwExportsNotFound (node:internal/modules/esm/resolve:472:9)
    at packageExportsResolve (node:internal/modules/esm/resolve:753:3)
    at packageResolve (node:internal/modules/esm/resolve:935:14)
    at moduleResolve (node:internal/modules/esm/resolve:1003:20)
    at defaultResolve (node:internal/modules/esm/resolve:1218:11)
    at ESMLoader.resolve (node:internal/modules/esm/loader:580:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:294:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
    at link (node:internal/modules/esm/module_job:78:36) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}
Enter fullscreen mode Exit fullscreen mode

Related:

Collapse
 
lioness100 profile image
Lioness100

WOW! I never thought of that use case, and your findings are really interesting! Thank you so much!! β™₯️β™₯️