How to create Vite based Vue component library consumable via .mjs files? - javascript

I created a Vite based Vue library. This library should distribute a single component inside a .mjs file.
Library setup
I basically created a new Vue app via npm create vue, moved every dependency to dev dependencies and added this to the package.json
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/computedRenderer.mjs",
"require": "./dist/computedRenderer.umd.cjs"
}
},
After that I added the following to my vite.config.ts file
build: {
lib: {
name: "mylib",
fileName: "computedRenderer",
entry: resolve(__dirname, './src/myComp/index.ts'), // index file exporting my component
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
}
Building the project should generate you a computedRenderer.mjs in the dist directory ( as expected ).
File server for testing purposes
I created a small Node server serving the static .mjs file
import cors from "cors";
import express from "express";
const port = 3_000;
express()
.use(express.json())
.use(cors())
.use(express.static('./public')) // directory contains the generated .mjs file
.listen(port, () => {
console.log(`Listening on port ${port}`);
});
When running the server and calling
http://localhost:3000/computedRenderer.mjs
you should get the file content
How I consume the component
In my main project I try to import the component dynamically during runtime like so
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(async () => {
const source = 'http://localhost:3000/computedRenderer.mjs'; // example url
const { ComputedRenderer } = await import(source);
// ...
});
</script>
Unfortunately I get the following warning
The above dynamic import cannot be analyzed by Vite. See https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations for supported dynamic import formats. If this is intended to be left as-is, you can use the /* #vite-ignore */ comment inside the import() call to suppress this warning.
and error
Uncaught (in promise) TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
Is my library setup wrong? Or how can I consume the component via url?
Please let me know if you need more information

Related

How to import TypeScript file with a .js extension using Vue CLI?

I want to do this:
import { pushState } from 'vue-router/src/util/push-state.js'
However, for some reason, the developers of vue-router wrote the push-state.js in TypeScript but left it with a .js extension. So when I first tried to import it, it gave me the following error when I ran vue-cli-service serve:
Module parse failed: Unexpected token (25:30)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> export function pushState (url?: string, replace?: boolean) {
I thus modified my vue.config.js like this:
module.exports = {
...
chainWebpack: config => {
...
config.module
.rule('ts')
.test(/push-state/)
.use('ts-loader')
.loader('ts-loader')
.end();
return config
}
}
and my tsconfig.json is as follows:
{
"include": [
"node_modules/vue-router/src/util/push-state.js",
"./node_modules/vue-router/src/util/push-state.js",
"./buffer.ts",
],
"exclude":[],
}
Now it does not throw any error, but pushState is simply undefined. I tried with require('vue-router/src/util/push-state.js') but it simply returns an empty object. I have ts-loader and typescript in my devDependencies.

Keep a ES module outside the bundle

My application has a configuration file
external-config.js
import {constants} from "./constants.js";
export const ExternalConfig = {
title: "My application",
version: "2.0",
constants: constants
list: ["uno", "due", "tre"]
}
I don't want it to be bundled with the application, I want to keep it outside. I tried the IgnorePlugin, but apparently this simply breaks the dependency graph and I get the error ReferenceError: Config is not defined even if the reference to the path of the config file in the budle is correct.
plugins: [
new webpack.IgnorePlugin({
checkResource (resource) {
if (resource === "./conf/external-config.js") return true;
return false;
}
})
]
I cannot even import it in the main html page like
<script type="module" src="./conf/config.js"></script>
because in this way I couldn't access the object outside its own script.
Is there a way to do that?
EDIT: following #raz-nonen advice, I tried null-loader, it seems it could be a solution. The problem with that is the physical position of the configuration file.
rules: [
{
test: path.resolve(__dirname, "src/conf/external-config.js"),
use: "null-loader"
}
...]
And this is the result in the built script
// EXTERNAL MODULE: ./src/conf/external-config.js
var external_config = __webpack_require__(13);
But the actual position of the configuration in the dist folder is ./conf/external-config.js, not ./src/conf/external-config.js
This is the chunk of my app that consumes the external file
import {ExternalConfig} from "./conf/external-config.js";
class MyApp extends LitElement {
constructor() {
super();
console.log(ExternalConfig.list)
}
}
You'll need to make this file available in the dist folder. You can do that with copy-webpack-plugin
Tell webpack that ExternalConfig will be imported from external. It means that you'll have to take care that it'll be available at runtime. You can do it simply by importing your conf/config.js that you copied from a <script> tag in your index.html.
Add:
externals: {
'conf/external-config': 'conf/external-config'
}
In your webpack configuration.

Webpack is erroring when I attempt to import a directory containing modules

I'm trying to create a small npm library to make interfacing with an API a little neater. My folder structure is as follows...
dist/
index.js
src/
index.js
endpoints/
endpoint1.js
package.json
webpack.config.js
Within my src/index.js file I have..
import {endpoint1} from './endpoints'
module.exports = class lib {
...
}
When I npm run build (which runs webpack --display-error-details --mode production) webpack throws a big error saying "Module not found: Error: Can't resolve './endpoints' in 'my\project\dir\src'.
My webpack.config.js file currently looks like...
const path = require('path');
module.exports = {
mode: 'production',
entry: path.join(__dirname, '/src/index.js'),
output: {
path: path.resolve('dist'),
filename: 'index.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /.js?$/,
exclude: /(node_modules)/,
use: 'babel-loader'
}
]
},
resolve: {
modules: [
path.resolve(__dirname, 'src/endpoints')
],
extensions: ['.js']
}
};
I can see similar questions have been asked before and the resolutions listed don't seem to work for me so I thought I'd post it incase im making a rookie error. If any more info is required just say! Sorry if it's fairly wall of texty. Thanks.
The correct import would be:
import endpoint1 from 'endpoint1';
By using resolve.modules you tell Webpack to look up non relative paths in that folder. The module name is "enpoint1".
But actually you should only do this with libraries that you use across your project, for an endpoint a relative import will be appropriate:
import endpoint1 from "./endpoints/endpoint1";
import {endpoint1} from './endpoints' means this:
import from file ./endpoints/index.js something that is exported under the name enpoint1 in that file. If you import directory then it refers to index.js under that directory, not to all other files. It doesn't exist in your setup.
Names inside {} refer to named imports. This goes only for es6 modules-style imports like import {...} from. If you ommit {} then you import the default. CommonJs-style imports like const {...} = require('') work differently. CommonJs does not have named imports and exports. It just will import default from that file and then fetch a field via object destructuring.
What you export is something unnamed(i.e. default) from file ./endpoints/enpoint1.js
Something is unnamed because you use module.exports = which is CommonJS-style export. CommonJS does not support named exports. This is equevalent to export default class lib ... in es6 modules-style exports.
IF you want to import many files under directory you can consider these solutions:
1) Often single import points are created. You make a index.js file. In it you import manually every file under the directoy that you want to export. Then you export it under names. Like this:
import a from './a.js';
import b from './b.js';
import c from './c.js';
export { a, b, c };
Then it will work
2) In some rare cases in might make sence to use fs.readdir or fs.readdirSync to scan the entire directory and dynamicly require files in a loop. Use it only if you must. E.g. db migrations.

Unable to bundle a Web Worker to be imported like an NPM package

My goal is to be able to publish a Web Worker NPM package which can be imported normally (import MyPkg from 'my-pkg') without requiring the user to import it with worker-loader (inline or otherwise)
To accomplish this, I've tried using a Babel build script as well as Webpack with worker-loader.
In the following examples there are two projects: the Web Worker package ("Package") which is npm linked to a test application ("App").
The Package is split into two files: entry.webpack.js and index.worker.js. The entry, when built and moved to /dist is designated as the main file in the package.json, and it currently looks like this:
entry.webpack.js
var MyPkg = require('worker-loader!./index.worker.js')
module.exports = MyPkg
index.worker.js
// This is just example code. It doesn't really matter
// what this code does so long as it ends up being run
// as a Web Worker.
var selfRef = self;
function ExampleWorker () {
console.log('Running Worker...');
setTimeout(function () {
// wait 10 seconds then post a message
selfRef.postMessage({foo: "bar"});
}, 10000)
}
module.exports = ExampleWorker
I then bundle the Package with Webpack:
package.json
"build": "rm -rf dist/*.* && webpack --progress"
webpack.config.js
module.exports = {
mode: 'production',
devtool: 'source-map',
entry: __dirname + '/src/entry.webpack.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
optimization: {
minimize: false
}
}
This generates two files: bundle.js and a Web Worker file as a hash: [hash].worker.js with the code we want evaluated in it. They key part in this, though, is that because we used worker-loader inline to import, the webpack compiled output looks something like:
module.exports = function() {
return new Worker(__webpack_require__.p + "53dc9610ebc22e0dddef.worker.js");
};
Finally, the App should be able to import it and use it like this:
App.js
import MyPkg from 'my-pkg'
// logging MyPkg here produces `{}`
const worker = new MyPkg()
// That throws an Error:
// Uncaught TypeError: _my_pkg__WEBPACK_IMPORTED_MODULE_4___default.a is not a constructor
worker.onmessage = event => {
// this is where we'd receive our message from the web worker
}
However, you can get it to work if, in the App itself you import the worker build like this:
import MyPkg from 'my-pkg/dist/53dc9610ebc22e0dddef.worker.js'
But, it's a requirement of the package to:
A) NOT require applications using the package to have to explicitly install worker-loader and
B) not have to reference the my-pkg/dist/[hash].worker.js explicitly.
I've tried also designating the built [hash].worker.js' as themain` in package.json but that doesn't work either.
Edit 1: I forgot to mention that I'm basing all of this off of how react-pdf does it. If you take a look in /src/entry.webpack.js and follow how it works throughout the package you'll see a few similarities.
you could try worker-loader with option:
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
name: '[name].[hash:8].js',
// notice here
inline: true,
fallback: false
}
}
},

Bundle a React component library with Rollup.js v1

I'm working on an Open Source D3/React component library and I'm trying to bundle the library using Rollup.js to offer code splitting, three shaking, etc.
The library is already published in GitHub and NPM and you can check a codesandbox using the library for a reference in case you want to try it.
Next I'm gonna try to highlight the different issues I'm experiencing with this bundle.
The library has already been tested using the code directly in a project and it works perfectly, so the problem is with the bundle and I assume that I'm doing something wrong with the Rollup.js configuration file in my project.
import { readdirSync } from 'fs';
import path from 'path';
import babel from 'rollup-plugin-babel';
import commonjs from 'rollup-plugin-commonjs';
import external from 'rollup-plugin-peer-deps-external';
import replace from 'rollup-plugin-replace';
import resolve from 'rollup-plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';
const CODES = [
'THIS_IS_UNDEFINED',
'MISSING_GLOBAL_NAME',
'CIRCULAR_DEPENDENCY',
];
const getChunks = URI =>
readdirSync(path.resolve(URI))
.filter(x => x.includes('.js'))
.reduce((a, c) => ({ ...a, [c.replace('.js', '')]: `src/${c}` }), {});
const discardWarning = warning => {
if (CODES.includes(warning.code)) {
return;
}
console.error(warning);
};
const env = process.env.NODE_ENV;
const plugins = [
external(),
babel({
exclude: 'node_modules/**',
}),
resolve(),
replace({ 'process.env.NODE_ENV': JSON.stringify(env) }),
commonjs(),
env === 'production' && terser(),
];
export default [
{
onwarn: discardWarning,
input: 'src/index.js',
output: {
esModule: false,
file: 'umd/silky-charts.js',
format: 'umd',
name: 'silkyCharts',
},
plugins,
},
{
onwarn: discardWarning,
input: getChunks('src'),
output: [
{ dir: 'esm', format: 'esm', sourcemap: true },
{ dir: 'cjs', format: 'cjs', sourcemap: true },
],
plugins,
},
];
The Errors
When I try to use the library using the production bundle (which is the default) directly from the NPM package I got the following error coming from one of the chunks node_modules/silky-charts/esm/chunk-501b9e58.js:5833
TypeError: react__WEBPACK_IMPORTED_MODULE_0___default(...) is not a function
If I stead use the development bundle I get a different error:
Failed to compile
../silky-charts/esm/index.js
Module not found: Can't resolve 'react' in '/Users/davidg/Development/personal/silky-charts/esm'
This error force me to install React, ReactDOM, and styled-components as devDependencies in the project for the library have access to these projects code.
After installing the devDependencies the error I get is the next one:
Hooks can only be called inside the body of a function component.
I already filled an issue in the React project page and according to them this is not a React issue but maybe a Webpack's since is usual to find this error when you have to install React in both the project and the library and Webpack finds there is two instances of React, and I kind of agree since the error varies depending on bundle type or the way the importer project is run as you can see in the codesandbox.
I hope you can help me to spot the error in the Rollup configuration file and if you feel like doing a PR in the project event better 😀.

Categories

Resources