How can I import an external file (config.js) from within a webpack bundle?
I have the following file-structure for local dev:
/dist/
/index.html
/plugin.js
/src/
/index.ts
/config.js # This config is only for local dev, will be different in prod
/ ...
The plugin.js will be moved to and executed by a third-party website.
I want to import the config.js (which will in production be provided by the third-party website) inside my plugin.js.
I import the config.js in index.ts like this:
import config from "../config.js";
I have managed to exclude the config.js from plugin.js by specifing it in the externals-field in the webpack.config.js which is working so far:
module.exports = {
...
externals: [
"../config.js"
]
}
When I open index.html however, the import statement from ../config.js is not getting an error but the config-object file is undefined.
The file structure of the third-party server in prod looks like this:
/ ... /
/plugins/
/other-plugin/... # A bunch of other plugins. Each has its own folder in plugins
/my-plugin/
plugin.js # This is my plugin
/config.js # This is the global config file of the server for all plugins
index.ts:
import config from "../config.js";
console.log(config);
config.js:
module.exports = {
foo: "bar"
}
The externals implies that whatever that was exported by config.js file would be available at runtime. So for browser, that means, you have probably already injected it via script tag or for Node.js, it is already imported via globalThis or some equivalent. Your import line - import config from "../config.js"; is simply gone when the code is bundled. The external doesn't mean that it would re-import config.js when the code is being run Additionally, the general practice is to use external object configuration instead of array configuration like:
module.exports = {
// ...
externals: {
"../config.js": "AppConfig"
}
};
This tells Webpack that whatever was exported by config.js file should be available as AppConfig object at runtime. Using array syntax is meant for code with side-effects.
Now coming back to possible solution. The recommended option is to use environment variables for passing the environment specific values. Assuming your plugin is a library, when it is being consumed and getting bundled as part of application via Webpack, you can make use of DefinePlugin to inject these values into your code. You can then access those in your code as process.env.foo. You should not import config.js in your code anywhere.
The second option is to use externals with object configuration as shown above. Change your config.js file to UMD or equivalent:
// config.js file
const config = {
foo: "bar"
};
// Make it available globally
window.AppConfig = config;
// Since `module` is not available in browser.
if (module && module.exports) {
module.exports = config;
}
And finally, import your config.js in index.html file before your bundled script gets loaded.
<head>
<script src="/path/to/config.js>"></script>
<script src="/path/to/bundle.js>"></script>
</head>
Related
I'm using Node 16.3.0 and Express 4.17.1 (though my Node version is flexible)
I have a session.js file that's like this:
// session.js
exports.fetchUserId = async function(token){
...
}
exports.save = async function(userId){
...
}
Then over in my app.js file, I try to do this:
// app.js
import session from './session.js'
I get the following error:
import session from './session.js'
^^^^^^^
SyntaxError: The requested module './session.js' does not provide an export named 'default'
...and then I want to use session in app.js like this:
let userId = await session.fetchUserId(req.body.token)
I have tried the following without any luck:
Adding "type": "module" to package.json
This answer by doing npm i esm
Using nodemon --experimental-modules app.js
My code works fine when used in a NuxtJS project, but I'm trying a vanilla Node/Express app and the named exports don't work.
Any idea what I'm doing wrong?
Problem in your code is that you are mixing the ES modules with CommonJS modules.
By default, node treats each javascript file as a CommonJS module but with the following in the package.json file
"type": "module"
you are telling node to treat each javascript file as a Ecmascript module but in session.js file, you are using the CommonJS module syntax to export the functions.
Solutions
You could solve your problem using one of the following options:
Change the extension of the session.js file to .cjs. (Don't forget to change the extension in the import statement in app.js file as well).
Changing the extension to .cjs will tell node to treat session.cjs file as a CommonJS module.
Just use ES modules and change the exports in session.js file as shown below:
export const fetchUserId = async function(token){ ... }
export const save = async function(userId) { ... }
and change the import statement in app.js file as:
import * as session from "./session.js";
I use npm and a gulpfile.js to essentially export npm packages to a 'lib' folder under 'wwwroot'; this works a treat and whenever I update a specific npm package if it's in my gulpfile.js watch list it'll push the contents to the 'lib' folder.
The issue I have is that I used to use a manually extracted copy of ocktokit-rest in order to query the public api for some repo data. Recently this has stopped working, I assume that GitHub has updated their api which has had some breaking changes for my old version of ocktokit-rest. So with that in mind I installed #Ocktokit/rest version 18.0.9 using the npm package.json. This then creates the following directory:
~/lib/#octokit/rest/
According to the docs I need to refence one of the index.js files inside this. So because Razor doesn't appreciate the use of the # symbol in the path I use the following in my _layout.cshtml
<script src="#Url.Content("~/lib/#octokit/rest/dist-src/index.js")" type="module"></script>
I added the type="module" as I was initially getting some issues with the import statements inside of the index.js file.
Here's the index.js file contents at the above route:
import { Octokit as Core } from "#octokit/core";
import { requestLog } from "#octokit/plugin-request-log";
import { paginateRest } from "#octokit/plugin-paginate-rest";
import { restEndpointMethods } from "#octokit/plugin-rest-endpoint-methods";
import { VERSION } from "./version";
export const Octokit = Core.plugin(requestLog, restEndpointMethods, paginateRest).defaults({
userAgent: `octokit-rest.js/${VERSION}`,
});
This then raises the following error in the chrome debugger:
Uncaught TypeError: Failed to resolve module specifier
"#octokit/core". Relative references must start with either "/", "./",
or "../".
I don't particularly like the idea of adjusting the #octokit/ reference in favour of '../../' because then every time my gulpfile.js npm push task runs I'll have to manually change this file. However for the sake of debugging this I went through and adjusted index.js to look like this:
import { Octokit as Core } from "../../core";
import { requestLog } from "../../plugin-request-log";
import { paginateRest } from "../../plugin-paginate-rest";
import { restEndpointMethods } from "../../plugin-rest-endpoint-methods";
import { VERSION } from "./version";
export const Octokit = Core.plugin(requestLog, restEndpointMethods, paginateRest).defaults({
userAgent: `octokit-rest.js/${VERSION}`,
});
When I did this I got similar error messages for each import that looked something like this:
index.js:4 GET
https://localhost:44364/lib/#octokit/plugin-rest-endpoint-methods
net::ERR_ABORTED 404
Now the above URL is pointed at the directory not a specific file, if I run the above through to a single file I can see it load in the browser and display the file. So If I type:
https://localhost:44364/lib/#octokit/plugin-rest-endpoint-methods/dist-src/endpoints-to-methods.js
I can see the js file displayed in the browser so I know it can be pathed to. Now Ideally I want to be able to use this package in another bit of custom js I wrote that iterates through my repos and creates nice little cards with all the info on, so I'm basically just trying to use it like this:
var octokit = new Octokit({ userAgent: 'agentName' });
But obviously the above is complaining about the existence of Octokit.
So I guess my question is, what the frack? I'm obviously missing something here so if anyone has any ideas in what direction I need to look or research I'd be very grateful.
It's probably nothing to do with the octokit package at all, and much more likely that I just don't understand how to properly import these types of JavaScript libraries into my asp .net core solution
There's a few parts of adding Octokit that you're having difficulties with: handling the # symbol, the scope at which you import it, and the fact that you're trying to use files intended for build tools.
# in a Razor Page
When you're writing JavaScript inline in a <script> tag inside the context of a Razor page, you'll need to escape the # character by using ##. For example, if you were referencing the Octokit path, you would write ##octokit/rest.
Scope
When you're using type=module, your code has module scope, making you unable to reference the Octokit variable outside of the module. In order to break out of module scope, you can attach the Octokit variable to the window object:
window.Octokit = new Octokit({ userAgent: 'agentName' });
Then later on, your code in other script blocks can access Octokit like normal:
const { data } = await Octokit.request("/user");
Building Octokit
The files you're importing are not intended for direct consumption by the browser. It's expecting you to be importing it into JavaScript build tools, not importing it as a module directly from the browser.
The index.js file you're trying to import client side is intended to be used with some JavaScript build tools like Webpack. To get this working the way you want to in gulp, you would need to modify your gulpfile.js to include some kind of a plugin that would import #octocat/rest and output it into a file usable by a browser.
To do this with Webpack, you need to install a Webpack plugin for gulp:
npm install --save-dev webpack-stream gulp-rename
Then, create a file next to your gulpfile.js called index.js that imports the library and does something with it:
import { Octokit } from "#octokit/rest"
window.Octokit = new Octokit({ userAgent: 'agentName' });
Now, modify your gulpfile.js to take index.js and pipe it through the webpack plugin:
const gulp = require('gulp');
const webpack = require('webpack-stream');
const rename = require('gulp-rename');
gulp.task('default', () =>
gulp.src(['index.js'])
.pipe(webpack())
.pipe(rename("index.js"))
.pipe(gulp.dest('lib/octokit/rest/'))
);
After running gulp, you should have an output file that has resolved all of the dependencies necessary for #octokit/rest!
Alternative Solutions
To save the trouble for this specific package, could you instead load Octokit from their CDN? The CDN handles all of the building and package resolution for you. Your code could then be:
<script type="module">
import { Octokit } from "https://cdn.skypack.dev/##octokit/rest";
window.Octokit = new Octokit({ userAgent: 'agentName' });
</script>
(Note that ## will escape the # sign on Razor pages.)
Most packages offer a CDN option for loading their library client side without having to mess around with build tools. Even if they don't officially offer a CDN, sites like jsdelivr or unpkg can still offer a way to import these files.
Sometime in the future, it looks like browsers might support import-maps. Then, you would be able to handle the package resolution through the browser and do something like this on your Razor page:
<script type="importmap">
{
"imports": {
"/##octokit": "/lib/##octokit/"
}
}
</script>
<script type="module">
import { Octokit } from '##octokit/rest';
var octokit = new Octokit({ userAgent: 'agentName' });
</script>
It looks like this might be usable with a polyfill like system-js, where you would add the s.js loader and replace importmap with systemjs-importmap.
I'm trying to import js library to use it in a Vue component.
From CDNs all js imports are working. But i can't use CDN for production.
if i use the way to import it globally i can see that my js file is replaced with html content... i guess it is because of a bad webpack config.
I also used the require('') way directly in my component but no success.
the only JS that is imported is app.js, but i can't find any config file that is calling this js or any webpack config that tells to merge js files in it...
i have a vue.config.js file like this i can't find any other config file for webpack in my project
module.exports = {
// proxy API requests to Valet during development
devServer: {
//proxy: 'http://api.mtm'
},
indexPath: 'public/index.html',
}
to import js file i've tried like this directly in my Vue component just after the export default {..
require('../../public/timer.js');
the error i have looks like that : TypeError: t is undefined
I have a Node.js project written in Typescript and I am trying to get it to work with mime.js (https://github.com/broofa/node-mime). I have a declaration file (https://github.com/borisyankov/DefinitelyTyped/blob/master/mime/mime.d.ts) but cannot get node.js in typescript to work nicely with it.
I have the following:
/// <reference path="../../libs/mime.d.ts" />
import mime = require( 'mime' );
This keeps the type script compiler happy and all is good until I try to run it in Node.
When I try to run it I get
Cannot find module 'mime'
However, if I edit the generated javascript to the following:
import mime = require( '../../libs/mime' );
It all works fine. If I change the typescript file to have the require with the relative path I get a compiler error:
Cannot find external module '../../libs/mime'
How do I refer Typescript to both the mime.d.ts file and to the mime.js file so that both the compiler and Node are happy.
Many Thanks
TypeScript supports relative paths only for TypeScript modules. Ambient JavaScript modules should be placed in node_modules folder. From Node.js doc:
If the module identifier passed to require() is not a native module, and does not begin with '/', '../', or './', then node starts at the parent directory of the current module, and adds /node_modules, and attempts to load the module from that location.
Workaround 1
Place .d.ts next to .js module and change it from:
declare module "mime" {
...
}
to:
declare module Mime {
...
}
export = Mime;
Workaround 2
Rewrite .d.ts files from:
declare module "mime" {
export function lookup(path: string): string;
...
}
to:
declare interface IMime {
function lookup(path: string): string;
...
}
And then use plain var instead of import:
var mime = <IMime>require( '../../libs/mime' );
Try the following: in the root of the project 'npm install mime'. It will create the node_modules directory with mime inside of it. You can import it by 'require('mime')'. You also have to 'reference' the .d.ts file but that's the minor thing, with the npm package node will be able to import the actual javascript lib.
I also recommend using TSD ('npm install tsd -g') for getting the .d.ts files from DefinitelyTyped.
Btw sry for typos, I'm writing from mobile.
you need:
Add definitions file(*.d.ts) of the library
You must understand, the definition file contains only the interfaces of the library. You should be add the library source code separately.
References:
The typescript definitions:
https://github.com/borisyankov/DefinitelyTyped
The definitions for mime:
https://github.com/borisyankov/DefinitelyTyped/blob/master/mime/mime.d.ts
I've found a better way of doing this that works with node_modules folders and typings folder. A working example can be seen here:
https://github.com/Roaders/contact-list-checker/tree/external-js-module-example
In this app I import the csv js library in the ContactList class:
import csv = require('csv');
This is after installing it:
npm install csv
as the csv lib is in the node_modules folder we do not need to specify and path to the lib and just csv is sufficient for the compiler to find it. Without a d.ts declaration file we get errors though. This declaration file could be located in the typings folder and come directly from tsd but this lib is not found on tsd so I had to create my own. csv.d.ts is in libs/csv:
declare module "csv" {}
(I will add function interfaces to this later)
the module name HAS to match the import name. The filename can be anything.
You can add a reference to this file in the file that's doing the import:
///<reference path="../../../libs/csv/csv.d.ts" />
or as I have done you can just add a reference to it in the tsconfig.json in the root of the project:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"diagnostics": true
},
"files": [
"./app.ts",
"./typings/tsd.d.ts",
"./libs/libs.d.ts"
]
}
(where libs.d.ts has a reference to csv.d.ts)
This method allows you to install js libs using npm install and use declarations from tsd.
I am use grunt-typescript to generate a single js file from a set of ts files. This works fine until I add an import statement to one of the ts files.
Example grunt-typescript config
typescript: {
server: {
src: ["./ts/file1.ts", "./ts/file2.ts"],
dest: "./js/out.js",
options: {
module: 'amd', //or commonjs
target: 'es5', //or es3
basePath: '',
sourcemap: false,
declaration: false,
ignoreError: false
}
}
}
If I add an import statement to the top of file2.ts e.g.
import PG = require("pg");
Then I get errors that code in File1.ts can't find types defined in File2.ts and I get an unexpected File2.js generated in the /ts directory ignoring the dest file parameter. The import seems to cause it to compile File2.ts totally separately.
Is this to be expected with import or how can I fix this to create the expected single js file without compile errors?
As soon as you import an AMD module, or export from outside of any internal module, your file will be compiled as an AMD module. AMD and single-file compilation are inherently different modes of working and don't like to be mixed. To read up on internal vs external modules check out this TypeScript wiki page.
You technically can still import AMD modules using the standard JavaScript method, but it's awkward. For example, using the require.d.ts file from DefinitelyTyped:
/// <reference path="require.d.ts"/>
require(["somemodule"], (SomeModule: namespace.SomeModule) => {
// async code called after the module is retrieved
});
Without the import keyword TypeScript does nothing about the require and leaves you on your own.
Alternately I would recommend going full AMD. If any of your libraries are AMD it's easier to work with, and you can still compile down to a single file when it's time to release.