Loading a static file in Angular during build - node.js

I'm want to load the contents of a file and inject it as a string in TypeScript at build time. I understand that this code would ordinarily be server code, but what I want is to have a build step that reads the file and injects its contents as a string.
import { readFileSync } from 'fs';
import { Component } from '#angular/core';
#Component({
template: `<pre>${readFileSync('./example.code')}</pre>`
})
export class ExampleComponent { }
Assuming example.code just has "Hello World" I would want this file to be built as:
template: `<pre>"Hello World"</pre>`
I have found babel-plugin-static-fs which I think should allow me to do this, but I was originally using ng (angular-cli) to build the project. I have done ng eject and updated webpack:
module: {
rules: [
/* snip */
{
"test": /\.ts$/,
"use": [
{
loader: "babel-loader",
options: {
plugins: ['babel-plugin-static-fs']
}
},
{
"loader": "#ngtools/webpack"
}, ] } ] }
However, when I run webpack, I still get
Cannot find module 'fs'
If I reverse the order of the loaders, it seems like babael does not like the # used in may annotations such as the #Component above so that loader does not work.
Is there any way to load a file as static content during an Angular project build?

The issue here is actually related to the tsconfig.app.json file that Angular creates and uses for AoT. This is separate from the tsconfig.json used to actually build the project which does load #types/node as expected.
If you've created a project with ng new, you can change tsconfig.app.json:
- "types": [],
+ "types": ["node"],
This will have the AoT compiler use the type definitions from #types/node.

Related

Bundle NPM Package so it has different import paths with Vite and Typescript

How can I bundle my NPM package in a way that I can have different import paths for different parts of the package? I have found webpack approaches, but I am using Vite and TS.
My package looks like this:
- src
- atoms
- molecules
- organism
- index.ts (currently simply imports and exports everything)
Now I can use this currently like this
import { Button } from '#mypackage/library'
How can I do it, so I get this outcome:
import { Button } from '#mypackage/library/atom'
Here is the relevant part of my package.json
{
"entry": "src/index.ts",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js",
"types": "./dist/index.d.ts"
},
"./package.json": "./package.json",
"./atoms": "./src/atoms/index.ts",
"./molecules": "./src/molecules/index.ts",
"./organisms": "./src/organisms/index.ts",
"./theme": "./src/theme/index.ts"
},
}
Here is my vite.config.ts
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
name: '#workdigtital/component-library-react',
fileName: (format) => `index.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
},
exports: 'named'
}
}
},
plugins: [react(), dts({ insertTypesEntry: true })],
resolve: {
alias: {
'#': path.resolve(__dirname, './src')
}
}
});
If I currently try an import like this, inside another project (Laravel+React), in which installed the library.
import { ThemeProvider } from '#workdigital/component-library-react/theme';
I get the following run time error (But no Typescript errors, even IntelliSense is working):
Failed to load url /resources/js/theme/ThemeProvider (resolved id: /resources/js/theme/ThemeProvider). Does the file exist?
My resulting Dist folder looks like this:
You can't have TypeScript exports, this simply won't work. An npm package should have only JS exports.
If you want to be able to selectively import different parts of your package, you must transpile them to different files.
rollup can do it, but it is lots of work, as you will have to set up a separate target for each exported file. Normally you use rollup to create a single bundle, this what this tool is made for.
tsc with a tsconfig.json will be a much better choice in your case. It does this by default, you only need to specify the output directory and it will produce a separate file for each source.
There is an excellent guide on the TypeScript site about packaging TypeScript libraries, you should probably start there.

Vite: Including files in build output

This is a Vue 3 + Vuetify + TS + Vite + VSCode project.
I'm trying to bundle an XML file in the production build. Some transformation needs to be applied on the file before spitting it out. Found this Vite plug-in that can do transformations. But unfortunately, it doesn't seem to touch XML files in any way. If I put my XML file in public folder, it gets copied to the build output, but is not processed by the transformation plugin. If I put it in assets or somewhere else under src, it is simply ignored.
How can I ask Vite to include certain file(s) in the build output and pass it through transformation?
Note: Before I migrated the project to Vite, I was using Vue 2 and WebPack, where I could use the well-known CopyWebpackPlugin to perform this transformation. Haven't been able to find locate its Vite equivalent till now.
You may want to just write a script to do the transformation and add it to your npm scripts. I created a simple chrome extension to play around with VITE. Having multiple html files was pretty simple:
import { defineConfig, BuildOptions } from 'vite'
import vue from '#vitejs/plugin-vue'
const { resolve } = require('path')
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
popup: resolve(__dirname, 'popup/index.html'),
options: resolve(__dirname, 'options/index.html'),
},
}
}
})
But I had to create a separate vite config file to process the background script since it had special configuration (didn't want hashing so I could specify the name in my manifest, esm module format), and it takes the typescript and outputs 'background.js' in the public folder:
import { defineConfig } from 'vite'
const { resolve } = require('path')
// https://vitejs.dev/config/
export default defineConfig({
build: {
emptyOutDir: false,
rollupOptions: {
input: resolve(__dirname, 'background.ts'),
output: {
format: "esm",
file: "public/background.js",
dir: null,
}
}
}
})
You could simply have the xml file in your src folder and run a special script (create a 'scripts' folder maybe) to do the transform and store the result in the public folder where vite will pick it up and copy it to the dist folder. Your 'build' script in package.json could look something like this:
"scripts": {
"build": "node scripts/transform-xml.mjs && vite build",
},
Author of the package has introduced a new option named replaceFiles in the version 2.0.1 using which you can specify the files that will be passed through the transform pipeline. I can now do the following in my vite.config.js to replace variables in my output manifest.xml file after build:
const replaceFiles = [resolve(join(__dirname, '/dist/manifest.xml'))];
return defineConfig({
...
plugins: [
vue(),
transformPlugin({
replaceFiles,
replace: {
VERSION_NUMBER: process.env.VITE_APP_VERSION,
SERVER_URL: process.env.VITE_SERVER_URL,
},
...
}),
...
});

How to configure Vite to output single bundles for a Chrome DevTools Extension?

I am trying to create a Chrome DevTools Extension with Vite.
There are a couple different entry points. The main two are src/background.ts and devtools.html (which references src/devtools.ts).
There are is some code that I want to shared between them in src/devtools-shared.ts.
After running the build, the entry points still contain import statements. Why and how do I get rid of them so they are self-contained bundles (Ideally not IIFE, just good old top level scripts)?
Here is what I have got:
vite.config.js:
const { resolve } = require('path')
const { defineConfig } = require('vite')
module.exports = defineConfig({
resolve: {
alias: {
"root": resolve(__dirname),
"#": resolve(__dirname, "src")
}
},
esbuild: {
keepNames: true
},
build: {
rollupOptions: {
input: {
'background': "src/background.ts",
'content-script': "src/content-script.ts",
'devtools': "devtools.html",
'panel': "panel.html",
},
output: {
entryFileNames: chunkInfo => {
return `${chunkInfo.name}.js`
}
},
// No tree-shaking otherwise it removes functions from Content Scripts.
treeshake: false
},
// TODO: How do we configured ESBuild to keep functions?
minify: false
}
})
src/devtools-shared.ts:
export const name = 'devtools'
export interface Message {
tabId: number
}
src/background.ts:
import * as DevTools from './devtools-shared'
src/devtools.ts:
import * as DevTools from './devtools-shared'
And then in dist/background.js I still have:
import { n as name } from "./assets/devtools-shared.8a602051.js";
I have no idea what controls this. I thought there would not be any import statements.
Does the background.ts entry point need to be a lib or something?
For devtools.html is there some other option that controls this?
I know there is https://github.com/StarkShang/vite-plugin-chrome-extension but this doesn't work very well with Chrome DevTool Extensions. I prefer to configure Vite myself.
It turns out that this is not possible. Rollup enforces code-splitting when there are multiple entry-points. See https://github.com/rollup/rollup/issues/2756.
The only workaround that I can think of is to have multiple vite.config.js files such as:
vite.config.background.js
vite.config.content-script.js
vite.config.devtools.js
Then do something like this in package.json:
"scripts": {
"build": "npm-run-all clean build-background build-content-script build-devtools",
"build-background": "vite build -c vite.config.background.js",
"build-content-script": "vite build -c vite.config.content-script.js",
"build-devtools": "vite build -c vite.config.devtools.js",
"clean": "rm -rf dist"
},
This is not very efficient as it repeats a lot of work between each build but that's a Rollup problem.

Vite library with Windicss

I am using Vite (Vue3) with Windi CSS to develop a library. I am using library mode for the build (https://vitejs.dev/guide/build.html#library-mode) with the following config:
vite.config.js
export default defineConfig({
plugins: [vue(), WindiCSS()],
build: {
lib: {
entry: path.resolve(__dirname, 'src/lib.js'),
name: 'MyLIB',
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['vue'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: 'Vue',
},
},
},
},
});
My entry file (src/lib.js) only includes a few Vue components in it and looks like this:
lib.js
export { default as AButton } from './components/AButton/AButton.vue';
export { default as ACheckbox } from './components/ACheckbox/ACheckbox.vue';
import 'virtual:windi.css';
import './assets/fonts.css';
When I build the library I get the js for just those components but the css is for every Vue file in the src folder and not only the ones i included in my lib.js file. I know the default behavior for Windi CSS is to scan the whole src folder but in this case, I only want it to scan the components I added to my entry.
Any ideas?
You should be able to restrict the scan by using extract.include and extract.exclude options, see there : https://windicss.org/guide/extractions.html#scanning
From the doc
If you want to enable/disable scanning for other file-types or locations, you can configure it using include and exclude options

Node.js: How to import test files in custom test runner

I'm trying to create my own custom testing framework for learning purpose. Test files are written in following way
import { somemethod } from './some/module'
test(/I click on a button)/, () => {
browser.get("someSelector").should("have.text",somemethod());
});
I user require(file) to load test files. But it throw error SyntaxError: Unexpected token {
for import statement in test file. I'm using node js version 11.15.
If I switch to node v13.14 and define "type": "module" in my package.json then it doesn't let me use require(file) to load a test file or any module in my package.
How can I import tests files considering the user may be importing the modules using import or require?
This answer is very empirical...
Considering that it works using canonical commonjs approach you can try to debug it with newer version of NODE (currently I would use 14). For it, I would suggest you to use a node version manager like NVM so you can switch between node version easily and test that accordling seeing differences between various node installations.
Make a minimal project with npm init with a single dependency, save your index with the .mjs extension and try an import the above dependency. If you are be able to import that dependency with that minimal environment you can blame either your previous node or your configuration or both of them.
At the moment you should only create a small 2 files project to reproduce the problem. It seems your current node does not consider the "type": "module" configuration and runs everything in its classic way.
Regarding your comments....
As far as I know import can be used even in your code, not just at the beginning:
(async () => {
if (somethingIsTrue) {
// import module for side effects
await import('/modules/my-module.js');
}
})();
from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Additionally you can try Webpack with a configuration like:
// webpack.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
mode: 'production',
target: 'node',
externals: [nodeExternals()],
entry: {
'build/output': './src/index.js'
},
output: {
path: __dirname,
filename: '[name].bundle.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['env', {
'targets': {
'node': 'current'
}
}]
]
}
}
}]
}
};
With NodeExternals you don't put your node dependencies in the bundle but only your own code. You refer to node_modules for the rest. You might not want that.

Resources