String replacements in index.html in vite - javascript

I am trying to inject some strings into the index.html of a Vite app (using vue3 template). In a vue-cli project for example we would have
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
What is the Vite way to do that? (I know that BASE_URL is just '/' in this case. I am asking for the general solution) I would be fine with a solution that covers environment variables only, but it would be great to know an even more general solution that can use JS code as in
<title><%= htmlWebpackPlugin.options.title %></title>
And I would really appreciate a solution that doesn't require installing an npm package

Had to lower my expectations considerably:
I install a package
I "cheat" and use process.env
// vite.config.js
import vue from '#vitejs/plugin-vue'
import { loadEnv } from 'vite'
import { createHtmlPlugin } from 'vite-plugin-html'
export default ({ mode }) => {
const env = loadEnv(mode, process.cwd())
return {
plugins: [
vue(),
createHtmlPlugin({
minify: true,
inject: {
data: {
title: env.VITE_MY_FOO,
}
}
}),
],
}
}
then in .env
VITE_MY_FOO="Hello vite ejs"
and in index.html
<title><%= title %></title>
Can't say I like it, but it works

Related

webpack vendor files failing on dependencies

I am trying to convert a existing dynamic web page, where everything from tables to high charts are inserted via js/html code, to be create by webpack. The js, css, images have all been modified successfully and the input files are being read in via ajax to populate the web page. The problem is the vendor js files. I am constantly getting error messages in the console about some js file function that is not defined or is not found with the 404 message. I have read the docs and reviewed existing posts for vendor, so it looks ok, but its just not working.
This is what I originally had in the html file:
<script type="text/javascript" src="js/jquery.min.1.8.2.js"></script>
<script type="text/javascript" src="js/underscore-min.js"></script>
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/papaparse.js"></script>
<script type="text/javascript" src="js/highcharts.js"></script>
<script type="text/javascript" src="js/modules/heatmap.js"></script>
<script type="text/javascript" src="js/modules/treemap.js"></script>
To get then imported into webpack I did the following:
index.htm (removed script type for js files since webpack will included them for me).
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="description" content="<%= htmlWebpackPlugin.options.metaDesc %>">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
index.js (this is the main file for webpack). Modules are for highchart and and load in by highchart.js.
import {readInSoftwareVersions} from "./softwareList"
import "./styles.css"
import "./vendor/js/jquery.min.1.8.2.js"
import "./vendor/js/underscore-min.js"
import "./vendor/js/react.js"
import "./vendor/js/highcharts.js"
import "./vendor/js/highcharts-more.js"
import "./vendor/js/papaparse.js"
import "./vendor/js/modules/broken-axis.js"
import "./vendor/js/modules/broken-axis.src.js"
import "./vendor/js/modules/canvas-tools.js"
import "./vendor/js/modules/canvas-tools.src.js"
import "./vendor/js/modules/data.js"
import "./vendor/js/modules/data.src.js"
import "./vendor/js/modules/drilldown.js"
import "./vendor/js/modules/drilldown.src.js"
import "./vendor/js/modules/exporting.js"
import "./vendor/js/modules/exporting.src.js"
import "./vendor/js/modules/funnel.js"
import "./vendor/js/modules/funnel.src.js"
import "./vendor/js/modules/heatmap.js"
import "./vendor/js/modules/heatmap.src.js"
import "./vendor/js/modules/no-data-to-display.js"
import "./vendor/js/modules/no-data-to-display.src.js"
import "./vendor/js/modules/solid-gauge.js"
import "./vendor/js/modules/solid-gauge.src.js"
import "./vendor/js/modules/treemap.js"
import './images/lessErrors.png'
import './images/moreErrors.png'
import './images/noChange.png'
/*
Read in software list and then build web page.
*/
$(document).ready(function(){
readInSoftwareVersions(false); // wait for json to be read in
});
webpack.config.js - I listed all the js files.
module.exports = {
mode: 'development',
entry: {
main: "./src/index.js",
vendor: [
"./src/vendor/js/jquery.min.1.8.2.js",
"./src/vendor/js/underscore-min.js",
"./src/vendor/js/react.js",
"./src/vendor/js/highcharts.js",
"./src/vendor/js/highcharts-more.js",
"./src/vendor/js/papaparse.js",
"./src/vendor/js/modules/broken-axis.js",
"./src/vendor/js/modules/broken-axis.src.js",
"./src/vendor/js/modules/canvas-tools.js",
"./src/vendor/js/modules/canvas-tools.src.js",
"./src/vendor/js/modules/data.js",
"./src/vendor/js/modules/data.src.js",
"./src/vendor/js/modules/drilldown.js",
"./src/vendor/js/modules/drilldown.src.js",
"./src/vendor/js/modules/exporting.js",
"./src/vendor/js/modules/exporting.src.js",
"./src/vendor/js/modules/funnel.js",
"./src/vendor/js/modules/funnel.src.js",
"./src/vendor/js/modules/heatmap.js",
"./src/vendor/js/modules/heatmap.src.js",
"./src/vendor/js/modules/no-data-to-display.js",
"./src/vendor/js/modules/no-data-to-display.src.js",
"./src/vendor/js/modules/solid-gauge.js",
"./src/vendor/js/modules/solid-gauge.src.js",
"./src/vendor/js/modules/treemap.js",
"./src/vendor/js/modules/treemap.src.js"
]
},
output: {
filename: "main-[contenthash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new HtmlWebpackPlugin({
hash: true,
title: 'Unit Test Results',
metaDesc: 'Display all the test results run on the select software version',
template: './src/index.htm',
filename: 'index.htm',
inject: 'body'
})
],
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader", // 2. Inject styles into DOM
"css-loader", // 1. Turns css into commonjs
]
},
{
test: /\.(png|jpg|gif)$/,
use: {
loader: "file-loader",
options: {
name: "./images/[name].[ext]",
}
}
},
]
}
};
There are no errors on npm compile for this setup. Now I thought web pack would handle the dependencies, but I am seeing modules/canvas-tools.js saying highcharts AddEvent is not defined, papaparse.js not found, and other errors about missing or not declared. Basically the js dependencies are messed up. Moving the import order around fixes one error but then some other dependency error happens because js files in module are calling each other.
So what am I doing wrong? Do I need some plugin for vendors? Currently I dumped the vendor logic and inserted the script type lines back into index.htm so it runs, but the point was to bundle the js files together using webpack.
Instead of manually creating a vendor bundle from an entry point, you should create a so called cacheGroup for which you define a test. Here's some example code from the docs, adjusted to your situation:
module.exports = {
...
entry: {
main: "./src/index.js"
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]vendor[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
...
};
This will add all imports from the vendor directory to a chunk called vendors. You can see the full example here.

Unable to change the publicPath in Vue 3 with Vite.js

I am trying to change the public path for production build but can't seem to get it to work. When I build for production, I would like to change the public path from / to ./ but I could not find anything in the documentation how to do this. I am using vite.js as my build tool. Maybe this is something Vue 3 and Vite specific, I'm not sure.
I have tried adding a vue.config.js file and also .env variables but so far nothing is working.
When I build, I get this output with path starting with /:
<head>
<script src="/assets/index.30c61b3b.js"></script>
<link href="/assets/vendor.29497e16.js">
<link href="/assets/index.7123a98f.css">
</head>
But for production I would like it to change to ./ like this:
<head>
<script src="./assets/index.30c61b3b.js"></script>
<link href="./assets/vendor.29497e16.js">
<link href="./assets/index.7123a98f.css">
</head>
I tried adding vue.config.js but it is not helping:
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? './' : '/'
}
and also .env
NODE_ENV=production
BASE_URL=./
I found the answer, I needed to add a base option to vite.config.js like this:
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
reactivityTransform: true
})
],
base: './',
})
https://vitejs.dev/config/#base

Unable to import css files into NextJs project

Currently I have my index.js file that has my display components. For CSS I have downloaded a css file from an external source. I am then adding the .css file into my styles folder that comes when you make the initial npx create-next-app. This file I'm then trying to import into my index.js file like so: import bootStyles from "../styles/bootstrap.css". But doing this gives me this error:
error - ./styles/bootstrap.css Global CSS cannot be imported from
files other than your Custom . Please move all global CSS imports
to pages_app.js. Or convert the import to Component-Level CSS (CSS
Modules). Read more: https://nextjs.org/docs/messages/css-global
Location: pages\index.js
Additionally I have also tried using the Head component like so:
import Head from 'next/head'
<Head>
<title>Create Next App</title>
<link rel="stylesheet" href="../styles/bootstrap.css"/>
</Head>
This doesnt show an error but the styles still dont reflect on my webpage.
Either put all your CSS imports in _app.tsx or you can add *.module.css to your filenames so they get picked up.
You can also override the nextjs webpack config:
// next.config.js
module.exports = {
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
return config
},
}
...
{
test: /\.s?[ac]ss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: [/node_modules/],
},
In order to override the module rules and css loader, however I wouldn't recommend this if you don't know what you're doing.

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 😀.

Universal React.JS - Requiring Styles and Images

Lately I've been dabbling with React and Webpack, and so far i've been loving it. Sure it took a lot of reading, looking at exemples, and trying some stuff out, but eventually, react itself as well as hot reloading my components grew on me and I'm pretty sure I want to continue that way.
For the past couple of days though I've been trying to make my pretty simple "application" (so far it's just a menu with a couple of sub components and react router to display a dummy page) render server side.
Here's my project's layout:
Here's what my webpack config looks like so far:
let path = require("path"),
webpack = require("webpack"),
autoprefixer = require("autoprefixer");
module.exports = {
entry: [
"webpack-hot-middleware/client",
"./client"
],
output: {
path: path.join(__dirname, "dist"),
filename: "bundle.js",
publicPath: "/static/"
},
resolve: {
root: path.resolve(__dirname, "common")
},
module: {
loaders: [
{
test: /\.js$/,
loader: "babel",
exclude: /node_modules/,
include: __dirname
},
{ test: /\.scss$/, loader: "style!css!sass!postcss" },
{ test: /\.svg$/, loader: "file" }
]
},
sassLoader: {
includePaths: [path.resolve(__dirname, "common", "scss")]
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
BROWSER: JSON.stringify(true),
NODE_ENV: JSON.stringify( process.env.NODE_ENV || "development" )
}
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
postcss: [autoprefixer({remove: false})]
}
And, omitting the module requirements, my server:
// Initialize express server
const server = express();
const compiler = webpack(webpackConfig);
server.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath }));
server.use(webpackHotMiddleware(compiler));
// Get the request
server.use((req, res) => {
// Use React Router to match our request with our components
const location = createLocation(req.url);
match({routes, location}, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
res.send(renderFullPage(renderProps));
} else {
res.status(404).send("Not found");
}
})
});
function renderFullPage(renderProps) {
let html = renderToString(<RoutingContext {...renderProps}/>);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">${html}</div>
<script src="/static/bundle.js"></script>
</body>
</html>
`;
}
server.listen(3000);
Little React Router server-side specificities here but I can get my head around it.
Then came an issue with requiring styles inside a component. After a while I figured out this bit (thanks to a github issue thread):
if (process.env.BROWSER) {
require("../scss/components/menu.scss");
logo = require("../images/flowychart-logo-w.svg") ;
}
And along with it the fact that I still need to declare a router on my client, re-using the routes my server already uses:
// Need to implement the router on the client too
import React from "react";
import ReactDOM from "react-dom";
import { Router } from "react-router";
import createBrowserHistory from "history/lib/createBrowserHistory";
import routes from "../common/Routes";
const history = createBrowserHistory();
ReactDOM.render(
<Router children={routes} history={history} />,
document.getElementById("app")
);
And now there I am. My style is applied and everything works. I'm still no expert, but so far I kind of understand everything that's going on (which is the main reason why I'm not using an existing universal js / react boilerplate - actually understanding what's going on).
But now my next issue to solve is how to require my images inside my components.
With this setup, it actually does work, however I'm getting this warning:
Warning: React attempted to reuse markup in a container but the
checksum was invalid.
This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting.
React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) "><img class="logo" src="/static/31d8270
(server) "><img class="logo" alt=""
And it makes sense, as you've seen in a previous code snippet, I actually set a logo variable when I'm in the browser environment, and I use that variable to set the src tag of my components. Sure enough on the server, no src attributes is then given, thus the error.
Now to my question:
Am i doing that style / image requirement right ? What am I missing to get rid of that warning ?
The one thing I love about that webpack / react association is how I can keep those dependencies close to the actual part of my UI that uses them. However the one thing I've found out was omitted from most of the tutorials out there was how to keep this sweetness working when rendering server side.
I'd be very grateful if you guys could give me some insight.
Thanks a lot !
I would recommend webpack-isomorphic-tools as a solution here.
I have a section on how to import SVG's into React apps in an example project: https://github.com/peter-mouland/react-lego#importing-svgs
Here is the code that was required to turn my Universal React app into one that accepts SVG's : https://github.com/peter-mouland/react-lego/compare/svg
There's at least two approaches you can take for this:
Try to de-webpackify your code to run in Node.
Compile your client code with Webpack using target 'node'.
The first option requires you to remove Webpack specific code such as require.ensure / System.import and teaching node your module resolution rules and how to convert non js assets into js. This is achievable with a few babel transformations and maybe modifications to the NODE_PATH however this approach does mean you're constantly chasing your tail, i.e. if you start using a new Webpack feature / loader / plugin you need to make sure you're able to support the equivalent functionality in Node.
The second approach is probably more practical as you can be sure that whichever Webpack features you're using will also work on the server. This is one way you may do it:
// webpack.config.js
module.exports = [
{
target: 'web',
entry: './client',
output: {
path: dist,
filename: 'client.js'
}
}, {
target: 'node',
entry: './server',
output: {
path: dist,
filename: 'server.js',
libraryTarget: 'commonjs2'
}
}
];
// client.js
renderToDom(MyApp, document.body);
// server.js
module.exports = (req, res, next) => {
res.send(`
<!doctype html>
<html>
<body>
${renderToString(MyApp)}`
<script src="/dist/client.js"></script>
</body>
</html>
`);
}
// index.js
const serverRenderer = require('./dist/server');
server.use(serverRenderer);
server.listen(6060);

Categories

Resources