There's an issue with importing es modules into the content script:
(How to import ES6 modules in content script for Chrome Extension)
Maybe there is a way to build the content script in one file to resolve this?
I'm using this boilerplate - https://github.com/JohnBra/vite-web-extension. Here's what my vite config looks like:
import react from "#vitejs/plugin-react-swc";
import { resolve } from "path";
import { defineConfig } from "vite";
import copyContentStyle from "./utils/plugins/copy-content-style";
import makeManifest from "./utils/plugins/make-manifest";
const root = resolve(__dirname, "src");
const pagesDir = resolve(root, "pages");
const assetsDir = resolve(root, "assets");
const outDir = resolve(__dirname, "dist");
const publicDir = resolve(__dirname, "public");
const IS_DEV = process.env.DEV === "true";
export default defineConfig({
resolve: {
alias: {
"#src": root,
"#assets": assetsDir,
"#pages": pagesDir,
},
},
plugins: [react(), makeManifest(), copyContentStyle()],
publicDir,
build: {
minify: !IS_DEV,
outDir,
sourcemap: IS_DEV,
emptyOutDir: IS_DEV,
rollupOptions: {
input: {
// devtools: resolve(pagesDir, 'devtools', 'index.html'),
// panel: resolve(pagesDir, "panel", "index.html"),
content: resolve(pagesDir, "content", "index.ts"),
background: resolve(pagesDir, "background", "index.ts"),
popup: resolve(pagesDir, "popup", "index.html"),
// newtab: resolve(pagesDir, "newtab", "index.html"),
// options: resolve(pagesDir, "options", "index.html"),
},
output: {
entryFileNames: (chunk) => {
return `src/pages/${chunk.name}/index.js`;
},
},
},
},
});
I tried having a separate vite config for the content scripts to build it in one file. But, I was not successful
Here's what the config for that looks like:
import { resolve } from "path";
import { defineConfig } from "vite";
import copyContentStyle from "./utils/plugins/copy-content-style";
import { viteSingleFile } from "vite-plugin-singlefile";
const root = resolve(__dirname, "src");
const pagesDir = resolve(root, "pages");
const assetsDir = resolve(root, "assets");
const outDir = resolve(__dirname, "dist");
const publicDir = resolve(__dirname, "public");
const IS_DEV = process.env.DEV === "true";
export default defineConfig({
resolve: {
alias: {
"#src": root,
"#assets": assetsDir,
"#pages": pagesDir,
},
},
plugins: [copyContentStyle(), viteSingleFile()],
publicDir,
build: {
minify: !IS_DEV,
outDir,
sourcemap: IS_DEV,
emptyOutDir: IS_DEV,
rollupOptions: {
input: {
content: resolve(pagesDir, "content", "index.ts"),
},
output: {
dir: `src/pages/content`,
},
},
},
});
I ended up having a separate vite config for the content script, which looks like:
import { resolve } from "path";
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
const root = resolve(__dirname, "src");
const pagesDir = resolve(root, "pages");
const outDir = resolve(__dirname, "dist", "src", "pages", "content");
const IS_DEV = process.env.DEV === "true";
export default defineConfig({
resolve: {
alias: {
"#src": root,
"#pages": pagesDir,
},
},
plugins: [viteSingleFile()],
build: {
copyPublicDir: false,
minify: !IS_DEV,
outDir,
sourcemap: IS_DEV,
emptyOutDir: IS_DEV,
rollupOptions: {
input: {
content: resolve(pagesDir, "content", "index.ts"),
},
output: {
entryFileNames: "index.js",
},
},
},
});
And, I run it all with:
"scripts": {
"build:general": "vite build",
"build:content": "vite build --config vite.contentScript-config.ts",
"build": "run-p build:*",
"dev:general": "vite build --watch --mode development",
"dev:content": "vite build --config vite.contentScript-config.ts --watch --mode development",
"dev": "run-p dev:*"
},
I'm trying to solve the Vite build error I get:
RollupError: Invalid value "iife" for option "output.format" - UMD and IIFE output formats are not supported for code-splitting builds.
The file name reported with this error points to
my web worker code, so I assumed that this setting belongs to the worker section in vite.config.ts:
import { defineConfig } from "vite";
import preact from "#preact/preset-vite";
import basicSsl from "#vitejs/plugin-basic-ssl";
import { NodeGlobalsPolyfillPlugin } from "#esbuild-plugins/node-globals-polyfill";
import { NodeModulesPolyfillPlugin } from "#esbuild-plugins/node-modules-polyfill";
import rollupNodePolyFill from "rollup-plugin-node-polyfills";
export default defineConfig({
plugins: [
preact(),
basicSsl(),
],
server: {
port: 3001,
https: true,
},
optimizeDeps: {
esbuildOptions: {
// Node.js global to browser globalThis
define: {
global: "globalThis",
},
// Enable esbuild polyfill plugins
plugins: [
NodeGlobalsPolyfillPlugin({
process: true,
buffer: true,
}),
NodeModulesPolyfillPlugin(),
],
},
},
worker: {
rollupOptions: {
output: {
format: "esm",
},
},
},
build: {
rollupOptions: {
plugins: [
// Enable rollup polyfills plugin
// used during production bundling
rollupNodePolyFill(),
],
output: {
format: "esm",
},
},
},
});
Additionally, I set the output format in the build rollup options. However, neither of the two settings are applied and I still get the said error.
What is the correct way to change the rollup output format setting in Vite?
The worker output format must be specified directly in the worker config key, not its rollup options:
import { defineConfig } from "vite";
import preact from "#preact/preset-vite";
import basicSsl from "#vitejs/plugin-basic-ssl";
import { NodeGlobalsPolyfillPlugin } from "#esbuild-plugins/node-globals-polyfill";
import { NodeModulesPolyfillPlugin } from "#esbuild-plugins/node-modules-polyfill";
import rollupNodePolyFill from "rollup-plugin-node-polyfills";
export default defineConfig({
plugins: [
preact(),
basicSsl(),
],
server: {
port: 3001,
https: true,
},
optimizeDeps: {
esbuildOptions: {
// Node.js global to browser globalThis
define: {
global: "globalThis",
},
// Enable esbuild polyfill plugins
plugins: [
NodeGlobalsPolyfillPlugin({
process: true,
buffer: true,
}),
NodeModulesPolyfillPlugin(),
],
},
},
worker: {
format: "es",
},
build: {
rollupOptions: {
plugins: [
// Enable rollup polyfills plugin
// used during production bundling
rollupNodePolyFill(),
],
output: {
format: "esm",
},
},
},
});
Vite produces unreasonably small chunks (90% of all chunks are under 1KB with a few 300KB+).
Here is the full build log:
https://gist.github.com/gajus/0149233592085181331dde7076fc50b1
This is example website:
https://contra.com/p/gkOQlbLq-validating-postgre-sql-results-and-inferring-query-static-types
This is out Vite configuration:
import path from 'path';
import { default as react } from '#vitejs/plugin-react';
import { defineConfig } from 'vite';
import { default as ssr } from 'vite-plugin-ssr/plugin';
const { VITE_BASE_URL } = process.env;
export default defineConfig(({ ssrBuild }) => {
let build;
if (!ssrBuild) {
build = {
emptyOutDir: true,
manifest: true,
minify: true,
polyfillModulePreload: false,
rollupOptions: {
output: {
chunkFileNames: 'chunk-[name].[hash].js',
entryFileNames: 'entry-[name].[hash].js',
inlineDynamicImports: false,
sourcemap: true,
},
},
sourcemap: true,
};
}
return {
base: VITE_BASE_URL ?? '/static/',
build,
plugins: [
react({
babel: {
plugins: [
'babel-plugin-relay',
[
'babel-plugin-styled-components',
{
displayName: true,
fileName: false,
pure: true,
ssr: true,
},
],
],
},
}),
ssr(),
],
resolve: {
alias: {
'#': path.resolve(__dirname, './src/'),
},
},
};
});
If you try to inspect this page using Google tools, it doesn't even load – presumably because of 200 JavaScript chunks that that page needs to load.
https://search.google.com/test/mobile-friendly/result?id=ULZhtOfQfp1Gxj2wYhyCUw
How to reduce the number of chunks?
I have an app folder with some images that are used by the external script and I need to include those images in the dist build folder.
I tried to log files that go to output and those images are not included. I tried to add assetsInclude property but seems that property is not for that purpose.
How can I include some specific images in dist folder that aren't imported explicitly ?
Here is my vite.config.js file.
import { resolve, parse } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
base: '/',
root: resolve(__dirname, 'app'),
assetsInclude: ['/app/images/externalImage.png'],
build: {
emptyOutDir: true,
rollupOptions: {
output: {
dir: './dist',
assetFileNames: (asset) => {
console.log(parse(asset.name).name);
if (parse(asset.name).name === 'externalImage') {
return "images/src/[name][extname]";
}
return "assets/[name].[hash][extname]";
}
},
},
},
});
In my js file which is included as a module inside the HTML file, I added string that you can see below. Here is details of how to expose the current module URL.
app/js/app.js
new URL("../images/src/externalImage.png", import.meta.url);
Also changed a bit config - added outDir prop:
import { resolve, parse } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
base: '/',
root: resolve(__dirname, 'app'),
build: {
outDir: '../dist',
emptyOutDir: true,
rollupOptions: {
output: {
assetFileNames: (asset) => {
if (parse(asset.name).name === 'externalImage') {
return "images/src/[name][extname]";
}
return "assets/[name].[hash][extname]";
}
},
},
},
});
Normaly it is really easy to add SCSS styles to components with angular components (just create the scss file and import it in the component.ts), however this styles are not rendered to normal css files, the styles are embeded (from what i understand, i am very new with angular).
This creates a problem, i am trying to use a theme with some dinamic skins with a "customization panel", but this component needs the path to my compiled css indepedently.
To achieve this, in the vendor basic app, i can see they added this to the angular.js:
{
...
"projects": {
"angular-starter": {
...,
"architect": {
"build": {
"builder": "#angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/angular-starter",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss",
{ "input": "src/vendor/styles/appwork.scss", "bundleName": "vendor/styles/appwork", "lazy": true },
{ "input": "src/vendor/styles/appwork-material.scss", "bundleName": "vendor/styles/appwork-material", "lazy": true },
{ "input": "src/vendor/styles/bootstrap.scss", "bundleName": "vendor/styles/bootstrap", "lazy": true },
{ "input": "src/vendor/styles/bootstrap-material.scss", "bundleName": "vendor/styles/bootstrap-material", "lazy": true },
// More styles like this
],
...
And then it can be referenced directly as css file, as the index shows:
<html lang="en" class="default-style">
<head>
...
<!-- Here it references the compiled scss as css directly -->
<link rel="stylesheet" href="vendor/styles/bootstrap.css" class="theme-settings-bootstrap-css">
<link rel="stylesheet" href="vendor/styles/appwork.css" class="theme-settings-appwork-css">
<link rel="stylesheet" href="vendor/styles/theme-corporate.css" class="theme-settings-theme-css">
<link rel="stylesheet" href="vendor/styles/colors.css" class="theme-settings-colors-css">
<link rel="stylesheet" href="vendor/styles/uikit.css">
...
<script>
// Here uses the path of the compiled css as parameter,
// this way the skin selector changes the css used in the page
window.themeSettings = new ThemeSettings({
cssPath: 'vendor/styles/',
themesPath: 'vendor/styles/'
});
</script>
</head>
...
</html>
However
Cheking the angular.js generated from jhipster, i can see the architect part is empty:
{
"$schema": "./node_modules/#angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"consuldent": {
"root": "",
"sourceRoot": "src/main/webapp",
"projectType": "application",
"architect": {}
}
},
I do not know if this is because in the example code it uses ng serve to run the demo page, using a node.js server, where jhipster uses spring directly, i tried adding the styles part to the jhipster's angular.js file, but i can not find any route where the css could be loaded, so im guessing it is just ignoring the code that i added
In angular, the view is encapsulated with each unique attribute.
add encapsulation: ViewEncapsulation.None in main component, hence the css will be shared by all components
import { Component, ViewEncapsulation } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
name = 'Angular app';
}
Jhipster uses webpack to pack the angular modules. You want to bundle the css hence
copy your css into /src/main/webapp/vendor/styles/appwork.css'
add an entry in webpack.common.js
new CopyWebpackPlugin([
{ from: './node_modules/swagger-ui/dist/css', to: 'swagger-ui/dist/css' },
{ from: './node_modules/swagger-ui/dist/lib', to: 'swagger-ui/dist/lib' },
{ from: './node_modules/swagger-ui/dist/swagger-ui.min.js', to: 'swagger-ui/dist/swagger-ui.min.js' },
{ from: './src/main/webapp/swagger-ui/', to: 'swagger-ui' },
{ from: './src/main/webapp/content/', to: 'content' },
{ from: './src/main/webapp/vendor/styles/appwork.css', to: 'content/vendor/styles/appwork.css' },
{ from: './src/main/webapp/favicon.ico', to: 'favicon.ico' },
{ from: './src/main/webapp/manifest.webapp', to: 'manifest.webapp' },
// jhipster-needle-add-assets-to-webpack - JHipster will add/remove third-party resources in this array
{ from: './src/main/webapp/robots.txt', to: 'robots.txt' }
]),
To compile custom sass
Add the entry in webpack.prod.js , for example myfile and myfile2. Then change the sass compiler to include our myfile.scss, myfile2.scss, finally comment the MiniCssExtractPlugin
entry: {
polyfills: './src/main/webapp/app/polyfills',
global: './src/main/webapp/content/scss/global.scss',
myfile: './src/main/webapp/content/scss/myfile.scss',
myfile2: './src/main/webapp/content/scss/myfile2.scss',
main: './src/main/webapp/app/app.main'
},
.
.
.
.
exclude: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
.
.
.
.
test: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
.
.
.
.
exclude: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/
.
.
.
.
test: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/,
.
.
.
.
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
// filename: '[name].[contenthash].css',
// chunkFilename: '[id].css'
}),
Your webpack.prod.js file look like this
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const Visualizer = require('webpack-visualizer-plugin');
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const AngularCompilerPlugin = require('#ngtools/webpack').AngularCompilerPlugin;
const path = require('path');
const utils = require('./utils.js');
const commonConfig = require('./webpack.common.js');
const ENV = 'production';
const sass = require('sass');
module.exports = webpackMerge(commonConfig({ env: ENV }), {
// Enable source maps. Please note that this will slow down the build.
// You have to enable it in UglifyJSPlugin config below and in tsconfig-aot.json as well
// devtool: 'source-map',
entry: {
polyfills: './src/main/webapp/app/polyfills',
global: './src/main/webapp/content/scss/global.scss',
myfile: './src/main/webapp/content/scss/myfile.scss',
myfile2: './src/main/webapp/content/scss/myfile2.scss',
main: './src/main/webapp/app/app.main'
},
output: {
path: utils.root('build/www'),
filename: 'app/[name].[hash].bundle.js',
chunkFilename: 'app/[id].[hash].chunk.js'
},
module: {
rules: [{
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
loader: '#ngtools/webpack'
},
{
test: /\.scss$/,
use: ['to-string-loader', 'css-loader', {
loader: 'sass-loader',
options: { implementation: sass }
}],
exclude: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
},
{
test: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: { implementation: sass }
}
]
},
{
test: /\.css$/,
use: ['to-string-loader', 'css-loader'],
exclude: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/
},
{
test: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
optimization: {
runtimeChunk: false,
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
minimizer: [
new TerserPlugin({
parallel: true,
cache: true,
terserOptions: {
ie8: false,
// sourceMap: true, // Enable source maps. Please note that this will slow down the build
compress: {
dead_code: true,
warnings: false,
properties: true,
drop_debugger: true,
conditionals: true,
booleans: true,
loops: true,
unused: true,
toplevel: true,
if_return: true,
inline: true,
join_vars: true
},
output: {
comments: false,
beautify: false,
indent_level: 2
}
}
}),
new OptimizeCSSAssetsPlugin({})
]
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
// filename: '[name].[contenthash].css',
// chunkFilename: '[id].css'
}),
new MomentLocalesPlugin({
localesToKeep: [
'en',
'es'
// jhipster-needle-i18n-language-moment-webpack - JHipster will add/remove languages in this array
]
}),
new Visualizer({
// Webpack statistics in target folder
filename: '../stats.html'
}),
new AngularCompilerPlugin({
mainPath: utils.root('src/main/webapp/app/app.main.ts'),
tsConfigPath: utils.root('tsconfig-aot.json'),
sourceMap: true
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
})
],
mode: 'production'
});
similarly do the same step for webpack.dev.js
const webpack = require('webpack');
const writeFilePlugin = require('write-file-webpack-plugin');
const webpackMerge = require('webpack-merge');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin');
const WebpackNotifierPlugin = require('webpack-notifier');
const path = require('path');
const sass = require('sass');
const utils = require('./utils.js');
const commonConfig = require('./webpack.common.js');
const ENV = 'development';
module.exports = (options) => webpackMerge(commonConfig({ env: ENV }), {
devtool: 'eval-source-map',
devServer: {
contentBase: './build/www',
proxy: [{
context: [
/* jhipster-needle-add-entity-to-webpack - JHipster will add entity api paths here */
'/api',
'/management',
'/swagger-resources',
'/v2/api-docs',
'/h2-console',
'/auth'
],
target: `http${options.tls ? 's' : ''}://127.0.0.1:8080`,
secure: false,
changeOrigin: options.tls,
headers: { host: 'localhost:9000' }
}],
stats: options.stats,
watchOptions: {
ignored: /node_modules/
}
},
entry: {
polyfills: './src/main/webapp/app/polyfills',
global: './src/main/webapp/content/scss/global.scss',
myfile: './src/main/webapp/content/scss/myfile.scss',
myfile2: './src/main/webapp/content/scss/myfile2.scss',
main: './src/main/webapp/app/app.main'
},
output: {
path: utils.root('build/www'),
filename: 'app/[name].bundle.js',
chunkFilename: 'app/[id].chunk.js'
},
module: {
rules: [{
test: /\.ts$/,
enforce: 'pre',
loader: 'tslint-loader',
exclude: [/(node_modules)/, new RegExp('reflect-metadata\\' + path.sep + 'Reflect\\.ts')]
},
{
test: /\.ts$/,
use: [
'angular2-template-loader',
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve('build/cache-loader')
}
},
{
loader: 'thread-loader',
options: {
// there should be 1 cpu for the fork-ts-checker-webpack-plugin
workers: require('os').cpus().length - 1
}
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
},
'angular-router-loader'
],
exclude: /(node_modules)/
},
{
test: /\.scss$/,
use: ['to-string-loader', 'css-loader', {
loader: 'sass-loader',
options: { implementation: sass }
}],
exclude: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
},
{
test: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/,
use: ['style-loader', 'css-loader', 'postcss-loader', {
loader: 'sass-loader',
options: { implementation: sass }
}]
},
{
test: /\.css$/,
use: ['to-string-loader', 'css-loader'],
exclude: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/
},
{
test: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/,
use: ['style-loader', 'css-loader']
}]
},
stats: process.env.JHI_DISABLE_WEBPACK_LOGS ? 'none' : options.stats,
plugins: [
process.env.JHI_DISABLE_WEBPACK_LOGS
? null
: new SimpleProgressWebpackPlugin({
format: options.stats === 'minimal' ? 'compact' : 'expanded'
}),
new FriendlyErrorsWebpackPlugin(),
new ForkTsCheckerWebpackPlugin(),
new BrowserSyncPlugin({
host: 'localhost',
port: 9000,
proxy: {
target: 'http://localhost:9060'
},
socket: {
clients: {
heartbeatTimeout: 60000
}
}
}, {
reload: false
}),
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)/,
path.resolve(__dirname, './src/main/webapp')
),
new writeFilePlugin(),
new webpack.WatchIgnorePlugin([
utils.root('src/test'),
]),
new WebpackNotifierPlugin({
title: 'JHipster',
contentImage: path.join(__dirname, 'logo-jhipster.png')
})
].filter(Boolean),
mode: 'development'
});
Then if you want the css to be include in your index.html then change the webpack.common.js
new HtmlWebpackPlugin({
template: './src/main/webapp/index.html',
chunks: ['vendors', 'polyfills', 'main', 'global','myfile','myfile2'],
chunksSortMode: 'manual',
inject: 'body'
})
Add your custom styles to src/main/webapp/content/scss/global.scss