(I have a Vue/Vite application but I think this question is generic to any setup.)
In my app, I have some code that I want to generalize into a separate npm library package so it can be used in multiple apps. This requires placing a file in the public folder of the app but I'm not sure what the best practice way of doing that is.
(This file is a service worker, but I think that is irrelevant to the question - this would be the same for any file that needs to have a public URL.)
I'd like the use of this library to be as clean as possible. That is, I'd like to not tell the client to "copy this file into your public folder".
I think that copy step needs to happen somewhere, though, unless there is a way to tell Vite to add a file to what it packages.
The copy step could be when running npm install, I guess. It would be nice if that could also add a line to .gitignore, too, if needed. I think this is complicated because the actual location is different whether the app is Vue, Nuxt, or some other framework.
I don't think the copy step could be when import mylib from "mylib" because that happens after the app is already packaged.
So, I think my two options are:
Tell Vite to put a particular file, unaltered, in the public folder.
Run a post install script when npm install runs. (That would have to work whether the user is on Windows, mac, or linux.)
Some other magic?
I don't know how to implement any of those, so a pointer to some documentation or a good term to search for would be appreciated, too.
Or is this just a fool's errand and I should tell the user to just copy the file to their public folder manually?
If you are using Vite, you can create an npm library with a Vite plugin in it. The documentation for Vite plugins is here, and the API that Vite's plugin API stems from is here. The code for a plugin to add to Vite's output and dev server would be
import { writeFile } from "fs";
import { resolve } from "path";
let url = "service-worker.js"
let fileContent = "file contents"
let outPath;
const myPlugin = () => ({
name: "add-service-worker",
configResolved(resolvedConfig) {
outPath = resolve(resolvedConfig.root, resolvedConfig.build.outDir, url);
},
configureServer(server) {
server.middlewares.use(`/${url}`, (req, res, next) => {
res.writeHead(200, { "Content-Type": "text/javascript" });
res.write(fileContent);
res.end();
});
},
closeBundle() {
writeFile(outPath, fileContent, { flag: "w" }, (error) => {
if (error) {
throw error;
}
console.log(`Generated file to ${outPath}`);
});
},
});
export default myPlugin;
The same could most likely be applied to a rollup or webpack plugin. However, the real easiest way would be to make this a library, and import it using
import service-worker from "service-worker"
Related
I'm trying to code in Typescript an NPM package that should work as a CLI tool.
For simplicity, let's say that the package will take the default export from a config.js file written by the dev using the package and print it in the console.
So the dev will:
create a config.js file with export default 'hello world'
type npx someLibrary and see "hello world" in the terminal.
This use case is common in tools I've used like babel, jest, and webpack. Although I've already overcome some technical challenges around that, I am stuck in how to grab the future config.js file in Typescript.
The approach I'm trying to pursue is using dynamic imports. The compiler won't allow it, since the config file doesn't exist at coding time. The code is something like this:
main();
async function main(): Promise<void> {
const config = await import("./config.js");
console.log(config);
}
This causes the compiler error: Cannot find module './config.js' or its corresponding type declarations.ts(2307).
My question is: how can I tell typescript to import the config file that will be created in the future and be available only during runtime?
If that is impossible, how can I consume the content of that config file inside the Typescript program?
My question is: how can I tell typescript to import the config file that will be created in the future and be available only during runtime?
a. For a quick solution, you can simply put #ts-ignore on the line above your import. This should ignore the compilation error, and at runtime the file should be imported accordingly.
b. Alternatively you may also use require('./config.js') instead of using import. This should also get rid of the compilation error.
c. For a more proper solution, you can look into using a function to read the file at runtime. Two things you can consider using are fetch and fs
fetch
fetch('./config.js')
.then(response => response.text())
.then(text => console.log(text))
fs
import fs from 'fs'
const data = fs.readFileSync('./config.js', 'utf8')
console.log(data)
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 have a Vue project using TypeScript and built by Vue-CLI3.
What I'm trying to achieve is to get Webpack to build separate bundles for my workers. I've read about Webpack Code Splitting and about configureWebpack in vue.config.js, but so far had no luck in putting them together.
The project setup is the standard vue create type. I have a ./src/main.ts as the main entry point and a bunch of TypeScript modules, I want as separate bundles with their own dependency trees (I'm fine with code duplication if it can't be avoided).
I'd like to get
./dist/js/all main stuff
./dist/js/workers/worker1.6e3ebec8.js
./dist/js/workers/worker2.712f2df5.js
./dist/js/workers/worker3.83041b4b.js
So I could do new Worker(worker1.6e3ebec8.js) in the main code.
I could launch workers from the main package by generating javascript code, putting it into a blob and instantiating from that, but it looks rather awkward. Besides, my worker code import other modules, so it doesn't seem to be an option anyway.
I'm quite new to all of this, so maybe I'm not even heading in the right direction.
What is the usual way of doing that on this stack?
You can use import(), it will return a Promise and will resolve your module.
As you are using Vue-CLI 3, webpack is ready and it should split your bundle automatically.
const moduleName = 'coolModuleName'
import (
/* webpackChunkName: "[moduleName]" */
`#/my/module/path/${moduleName}.js`
).then(moduleCode => {
// use your module
})
// load them in parallel
const getModuleDynamically(path, moduleName) => import(
/* webpackChunkName: "[moduleName]" */
`#/${path}/${moduleName}.js`
)
Promise.all([
getModuleDynamically(path, moduleName1),
getModuleDynamically(path, moduleName2),
getModuleDynamically(path, moduleName3)
])
Got there! #aquilesb's answer did help, although I've failed to get getModuleDynamically() from the answer working after plenty of experimenting.
Update: Looks like with this solution I'm not able to use imports of npm modules. I've tried experimenting with worker-loader but haven't got anywhere so far.
Here are a few takeaways:
Create a separate webpack config for packing workers. The target: 'webworker' must be there. Call it with webpack --config ./webpack.config.workers.js, as Vue wouldn't know about that.
Create a separate tsconfig.json for workers TypeScript. The lib setting for workers must be there: "lib": ["esnext","webworker","scripthost"] as well as the proper include:[...]/exclude:[...] settings.
You may need to tell Vue to use the main tsconfig.json that has it's own "lib":["esnext","dom","dom.iterable","scripthost"] and include/exclude. This is done in vue.config.js, you will probably need to create it. I use chainWebpack configuration option of Vue config.
Let Webpack know you have dynamic loading by making calls to import() with static (i.e. not variable) names. I haven't found a way to do so in config file, but it doesn't matter: you can't help hardcoding the names somewhere, how else Webpack would know it has to bundle and split the code?
Somehow get the name(s) of generated files, as you must have at least one of them at runtime to do new Worker(filename). I used the --json option of Webpack CLI for that.
There are many ways all of this can be achieved. This is what this ended up looking like in my project:
Folder structure:
webpack.config.workers.js
vue.config.js
tsconfig.base.json
src/main/
src/main/tsconfig.json -- extends tsconfig.base.json
src/shared/ -- this code may be duplicated by the Vue app bundles and by workers bundle
src/workers/
src/workers/tsconfig.json -- extends tsconfig.base.json
webpack.config.workers.js: contains a single entry – the main worker file, that loads the other stuff.
entry: {
worker: './src/workers/worker.ts'
}
build.workers.sh: the script calls Webpack CLI and produces a JSON file with the resulting workers names (trivial actions on folders are omitted). The only one I need is called "worker". The rest is to be dynamically loaded by it.
#!/bin/bash
# Map entry name -> bundle file name
# "assetsByChunkName":{"entryN":"entryN.[hash].js", ...}
json=$(webpack --config ./webpack.config.workers.js --json $#|tr -d "\n\r\t "|grep -Eo '"assetsByChunkName":.+?}')
# Remove "assetsByChunkName"
json=$(echo "${json:20}")
echo $json
echo $json > "$target/$folder/workers.json"
Load workers.json at runtime. The other option would be to use it at compile time by providing Vue config with const VUE_APP_MAIN_WORKER = require("path to/workers.json").worker and using this env constant.
Now that we have the name of the main worker file, we can do new Worker("main worker file path we've got from webpack").
The main worker file contains the function that statically references other modules and dynamically loads them. This way Webpack knows what to bundle and how to split the code.
enum WorkerName {
sodium = "sodium",
socket = "socket"
}
function importModule(name: WorkerName): Promise<any> {
switch (name) {
case WorkerName.sodium:
return import(
/* webpackChunkName: "sodium" */
"workers/sodium"
);
case WorkerName.socket:
return import(
/* webpackChunkName: "socket" */
"workers/socket"
);
}
}
Use the postMessage/message event API to tell your main worker code what to load.
const messageHandler = (e: MessageEvent) => {
// here goes app-specific implementation of events
// that gets you the moduleName in the end
importModule(moduleName).then((work) => {
// do smth
});
};
Now, to the correct answer.
To achieve the following:
Using webworkers
Using both dynamic imports and normal imports in webworker code
Sharing code between webworkers and main app
I had to add a separate rule for worker-loader in vue.config.js and also to add babel-loader. It took me some time to find the correct solution, but I dropped the previous one (in my other answer) in the end. I still use separate tsconfig.js for main and for webworkers.
What I'm still not happy with, is that vue-cli–or rather fork-ts-checker plugin–doesn't seem to know the webworker-specific types in my worker classes (so I can't use DedicatedWorkerScope, for instance).
I have a React application created using the create-react-app module. I have recently been asked by a client to integrate with oidc. For this purpose I'm using redux-oidc, as I already have redux working in my app as well.
We managed to integrate my application into their Identity server and I'm able to sign in and get the user token stored in redux. The problem is that I'm struggling to setup silent renew in my create-react-app application as I have to add an additional entry point. Is there a way to add an additional entry point to silent_renew/index.js without ejecting create-react-app?
Currently I've create a folder called silent_renew containing an index.js file. This folder also contains a silent_renew.html file with not much in it (See: example app similar to my folder structure).
Since the landing page for silent_renew is a just a simple html page, you could bypass webpack. Just put the following file in the public folder. Also, include a copy of the oidc-client.min.js library in the same folder.
public/silent_renew.html:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="oidc-client.min.js"></script>
<script>
new Oidc.UserManager().signinSilentCallback().then()
</script>
</body>
</html>
This works at my site in the develepment config. For the production config I have the following in mind (I did not test it yet but I'm pretty confident this is the way forward...).
public/index.js
const express = require('express')
const path = require('path')
const app = express()
app.use(express.static('.'))
app.use((req, res, next) => {
if (path.extname(req.path).length > 0) {
next()
} else if (path.dirname(req.path).indexOf('silent_renew') > -1) {
req.url = '/silent_renew.html'
next()
}
else if (path.dirname(req.path).indexOf('callback') > -1) {
req.url = '/callback.html'
next()
} else {
req.url = '/index.html'
next()
}
})
app.listen(3000)
As soon as create-react-app supports multiple entry points (I hope this happens soon for enterprise login scenario's) this code becomes obsolete.
You can also take the approach of loading the main bundle in the iframe and capturing the path as mentioned here.
Then you don't need to deal with exposing a path to load the oidc client lib (oidc-client.min.js or redux-oidc.js) or dumping it's content somewhere.
index.js/ts
import * as React from 'react';
import { render } from 'react-dom';
import { processSilentRenew } from 'redux-oidc';
import App from './App';
if (window.location.pathname === '/silent-renew') {
processSilentRenew();
} else {
render(<App />, document.getElementById('root'));
}
Please note that /silent-renew request performance can be potentially negatively impacted by large files that loaded along with the application. Some thoughts on it in the comment.
I got it to work by simply adding a route instead of using a separat endpoint. My setup is a create-react-app with redux, redux-oidc & react-router.
I configured the UserManager to use
{
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ""}/silent_renew`
}
I added this route in my react-router:
<Route exact={true} path={"/silent_renew"} component={SilentRenewComponent} />
The SilentRenewComponnent is a simple function component which calls the redux-oidc function to process the redirect.
import React from "react";
import { processSilentRenew } from "redux-oidc";
export const SilentRenewComponent = () => {
processSilentRenew();
return(
<div>SilentRenewComponent</div>
);
};
As previously said, changing the webpack configuration of create-react-app would defies what the purpose of this project is, providing a way to do React with zero-configuration.
I'm afraid the solution would be to eject your app, which is irreversible.
After doing so, create the silent_renew directory at the root of your project with the index.js and index.html file, create a store for redux as seen here (you probably don't need a lot of this stuff, like sagas, the router and logger middleware, just take the loadUser logic and store creation), import the store in the src/index.js file and create a redux Provider, like this.
Then, you can modify config/webpack.config.dev.js and follow what we can see at the redux-oihc-example webpack conf. Add HtmlWebpackPlugin and CommonsChunkPlugin for silentRenew, and the additional entry point.
What's a bit upsetting about CRA is that their webpack configuration for dev and prod are totally separated and do not extend a shared one. You'll have to do this operation in both prod and dev configs, or extend one other conf file to prevent redundancy, like this for example.
I would also advice you to use another simple scaffold, CRA is good when you don't have anything special to do unlike what you want (and probably more in the future). Ejecting will create a lot of files and code that you don't even need in your own codebase. A friend and I made a minimalist one, but I'm sure there is plenty of better alternatives.
You cannot add a custom webpack loader if you haven't ejected:
We don't intend to provide Webpack specific overrides because it will be very fragile. People will start depending on specific loaders and plugins and we won't be able to improve the overall experience.
source: https://github.com/facebookincubator/create-react-app/issues/99#issuecomment-234657710
If you want to add a new specific entry file you first need to yarn eject then edit config/webpack.config.dev.js:34 and config/webpack.config.prod.js:55.
The same is valid for adding new webpack loaders.
I have a programm where I need to have long multi line strings. It's a pain to store them in the .js document, because js doesn't have multi line strings and I end up having a twice as long as the screen width line looking as ugly as "This is an example.\n"
Is there a way to have a txt file, from where I can import strings with new lines (or at least just import strings)?
There is a Meteor Assets object that allows you to read files in the private directory of your app, in the following way for example for text files.
Assets.getText("foo.txt", function (err, res) { ... });
See full documentation: http://docs.meteor.com/#assets
Previous answer works only for public files. If you want to access file data that is visible only on the server you should probably use 'fs' npm module. It's described in details here: http://www.eventedmind.com/posts/meteor-file-uploader-part-2-server-side-save
The meteor-yaml package makes this easy - it automatically loads any .yaml files in your project, parses them into JavaScript objects, and makes them available in YAML.data.
In my application I have some code outside of the meteor app that needs the same settings, so I prefer to have the config file outside of the meteor project directory. Then I load the file like this:
var fs = Npm.require('fs');
fs.readFile('<path to file>.yaml', 'utf8', function(err, data) {
if(err) {
//Throw exception if the file is missing
throw new Error("Missing config file")
}
else {
//Read the file into a JavaScript object
config = YAML.parse(data);
}
});
Unfortunately, the meteor-yaml package is a little out of date with how the meteor team wants node packages to be loaded now, so if you're using a recent version of meteor the package won't work out of the box.
I filed a bug about this, but in the meantime to get around it I installed it as a private package, as opposed to installing it from atmosphere, and fixed the bug. To do this:
Clone the repo under your projects packages/ directory
Comment out the Npm.require lines.
Add a call to depends:
Npm.depends({yamljs: "0.1.4"});
Run meteor. Meteor will detect the meteor-yaml private package and install the dependencies.