I'm using webpack 5 to bundle an embeddable widget.
I can't seem to load an image created on the fly.
const img = document.createElement('img');
img.src = 'assets/doggo.svg';
http://localhost:8080/assets/doggo.svg gives 404
In my webpack.config.js I have the following:
module: {
rules: [
// Typecript
{
test: /\.ts|\.tsx$/,
use: 'awesome-typescript-loader',
include: __dirname
},
// Images
{
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
type: 'asset/resource',
},
// Fonts and SVGs
{
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
type: 'asset/inline',
},
],
},
Below is my file structure:
├── package.json
├── postcss.config.js
├── src
│ ├── index.ts
│ ├── assets
│ ├── index.html
│ ├── index.ts
│ ├── types.d.ts
│ └── utils
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
The approach below seems to work.
import doggoImg from './assets/doggo.svg';
const img = document.createElement('img');
img.src = doggoImg;
const container = document.createElement('div');
container.appendChild(img);
Related
I currently am trying to build a UI Library for React and I am having a little bit of trouble. Currently I am using typescript and rollup, and I am able to bundle a single index.js and I am able to import those components but it is importing the whole library.
Currently:
File structure:
src
--components
-----button
-------button.tsx
-------button.types.ts
-----input
-------input.tsx
-------input.types.ts
-----index.ts
rollup.js
My rollup targets index.ts which has everything exported like so:
export { default as Button} from './button/button'
export { default as Input } from './input/input'
And I am able to import in a react project like so:
import { Button, Input } from 'my-library'
What I would Like to do
I would like that each component is bundled separately and they would be imported like so
import { Input } from 'my-library/input'
import { Button } from 'my-library/button'
What I've Tried:
After reading the docs, it seemed that the preserveModule: true is what I was looking for but then I tried importing as above but it started to complain that nothing was found.
My current rollup.js looks like this:
export default {
input: 'src/index.ts',
output: [
{
exports: 'named',
dir: 'build/',
format: 'esm',
sourcemap: true,
preserveModules: true,
},
],
plugins: [
cleaner({ targets: ['./build'] }),
peerDepsExternal(),
resolve(),
commonjs(),
terser(),
typescript({
exclude: ['**/*.stories.tsx', '**/*.test.tsx'],
}),
],
};
EDIT: I've posted a more comprehensive tutorial on medium here
I tried using preserveModules but it doesnt generate an index.js file for each Components such that I can import like so :
import Button from 'lib/Button'
Hence I came up with a work around to make rollup loop through my src folders to generate a folder with entrypoint for every Component folder i had in src at rootDir
Maintain a strict folder structure with entry point for every Component folder. Do not have loose files, other than index.ts in src folder that have no folders. Name your folders properly like how you want users to import it
src folder structure:
rollup.config.js
src
├── Accordion
│ ├── Accordion.tsx
│ ├── AccordionBody.tsx
│ ├── AccordionButton.tsx
│ ├── AccordionCollapse.tsx
│ ├── AccordionContext.ts
│ ├── AccordionHeader.tsx
│ ├── AccordionItem.tsx
│ ├── AccordionItemContext.ts
│ └── index.ts
├── Alert
│ ├── Alert.tsx
│ └── index.ts
├── Badge
│ ├── Badge.tsx
│ └── index.ts
├── Breadcrumb
│ ├── Breadcrumb.tsx
│ ├── BreadcrumbItem.tsx
│ └── index.ts
├── Button
│ ├── Button.tsx
│ └── index.ts
├── ButtonGroup
│ ├── ButtonGroup.tsx
│ └── index.ts
...
├── Tooltip
│ ├── Tooltip.tsx
│ ├── TooltipBox.tsx
│ └── index.ts
├── index.ts
Its crucial for this case to maintain an entry point for each Component folder. I still maintained an entry point for src folder so that users can still import multiple components from the library with one line
i.e. import {Button, Accordion, ...} from 'lib'
Rollup config
getFolders returns an array of Folder names that are meant for export
loop through getFolders to generate the rollup obj per folder.
For typescript projects, rollup outputs the typings file with preserved folder structure already, so I realised that the folders Accordion, Button etc. were already there with typings file only. Now we need to add the index.js file to it!
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import typescript from 'rollup-plugin-typescript2';
import replace from '#rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';
const packageJson = require('./package.json');
import { getFolders } from './scripts/buildUtils';
const plugins = [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({
tsconfig: './tsconfig.json',
useTsconfigDeclarationDir: true,
}),
terser()
]
const getFolders = (entry) => {
// get the names of folders and files of the entry directory
const dirs = fs.readdirSync(entry)
// do not include folders not meant for export and do not process index.ts
const dirsWithoutIndex = dirs.filter(name => name !== 'index.ts').filter(name => name !== 'utils')
// ['Accordion', 'Button'...]
return dirsWithoutIndex
}
//loop through your folders and generate a rollup obj per folder
const folderBuilds = getFolders('./src').map(folder=> {
return {
input: `src/${folder}/index.ts`,
output: {
// ensure file destination is same as where the typings are
file: `dist/${folder}/index.js`,
sourcemap: true,
exports: 'named',
},
plugins,
external: ['react', 'react-dom'],
}
})
export default [
{
input: ['src/index.ts'],
output: [
{
file: packageJson.module,
format: 'esm',
sourcemap: true,
exports: 'named',
},
],
plugins,
external: ['react', 'react-dom'],
},
...folderBuilds,
{
input: ['src/index.ts'],
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: true,
exports: 'named',
},
],
plugins,
external: ['react', 'react-dom'],
},
];
CJS file
finally i also added the rollup config to generate the cjs file. I did not bother to code split the cjs file since most users are using es6 imports
"frank" build
Post build, I run a script to copy paste package.json, Readme to the ./dist folder
/* eslint-disable no-console */
const { resolve, join, basename } = require('path');
const { readFile, writeFile, copy } = require('fs-extra');
const packagePath = process.cwd();
const distPath = join(packagePath, './dist');
const writeJson = (targetPath, obj) =>
writeFile(targetPath, JSON.stringify(obj, null, 2), 'utf8');
async function createPackageFile() {
const packageData = await readFile(
resolve(packagePath, './package.json'),
'utf8'
);
const { scripts, devDependencies, ...packageOthers } =
JSON.parse(packageData);
const newPackageData = {
...packageOthers,
private: false,
typings: './index.d.ts',
main: './main.js',
module: './index.js',
};
const targetPath = resolve(distPath, './package.json');
await writeJson(targetPath, newPackageData);
console.log(`Created package.json in ${targetPath}`);
}
async function includeFileInBuild(file) {
const sourcePath = resolve(packagePath, file);
const targetPath = resolve(distPath, basename(file));
await copy(sourcePath, targetPath);
console.log(`Copied ${sourcePath} to ${targetPath}`);
}
async function run() {
try {
await createPackageFile();
await includeFileInBuild('./README.md');
// await includeFileInBuild('../../LICENSE');
} catch (err) {
console.error(err);
process.exit(1);
}
}
run();
finally from root npm publish ./dist
This is how my dist folder looks like finally
dist
├── Accordion
│ ├── Accordion.d.ts
│ ├── AccordionBody.d.ts
│ ├── AccordionButton.d.ts
│ ├── AccordionCollapse.d.ts
│ ├── AccordionContext.d.ts
│ ├── AccordionHeader.d.ts
│ ├── AccordionItem.d.ts
│ ├── AccordionItemContext.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Alert
│ ├── Alert.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Badge
│ ├── Badge.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Breadcrumb
│ ├── Breadcrumb.d.ts
│ ├── BreadcrumbItem.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Button
│ ├── Button.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── ButtonGroup
│ ├── ButtonGroup.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
...
├── Tooltip
│ ├── Tooltip.d.ts
│ ├── TooltipBox.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── index.d.ts
├── index.js
├── index.js.map
├── main.js
├── main.js.map
├── package.json
I got my solutions after much research from rollup issue thread on gh.
Here are some references:
Franking the build : https://stackoverflow.com/questions/62518396/importing-from-subfolders-for-a-javascript-package#:~:text=Votes-,13,-This%20is%20possible
folder structuring :
https://github.com/ezolenko/rollup-plugin-typescript2/issues/136#issuecomment-792383946
inspiration for getFolders() that i wrote was from this author's getFiles()
https://www.codefeetime.com/post/rollup-config-for-react-component-library-with-typescript-scss/
I'm working on a project voplayer and I need a dev mode with hot reloading. My project is about a video player and in order to test my library, I use a basic index.html containing a video tag (and it includes also a script index.js to use my library).
Here is my problem : if I add a script tag such as <script src="voplayer.js"></script> it works fine.
But there is no possibility for hot reloading. That's why I want to use html-webpack-plugin to dynamically build my library and inject it into my index.html file.
Here is my webpack.config.js :
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: "./index.ts",
mode: "development",
devServer: {
contentBase: path.join(__dirname, '/'),
// historyApiFallback: {
// rewrites : [ { from: /.*/, to: 'index.html' } ],
// },
compress: true,
hot : true,
port: 9000,
},
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
},
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
loader: "babel-loader",
options: { presets: ["#babel/env"] }
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.ts$/,
loader: 'ts-loader',
exclude: /(node_module)/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
fallback : {
"stream": require.resolve("stream-browserify"),
"buffer": require.resolve("buffer/")
}
},
output: {
path: __dirname + '/dist',
filename: 'voplayer.js',
library: 'voplayer',
libraryTarget: 'umd',
umdNamedDefine: true
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'samples/simple/index-template.html',
inject: true
})
]
};
And here is my basic samples/simple/index-template.html :
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'/>
<title>voplayer.js example</title>
<!-- built files will be auto injected -->
<style>
video {
width: 1280px;
height: 720px;
}
</style>
</head>
<body>
<p>Hello</p>
<div id='player' style='position: relative;'>
<video controls autoplay></video>
</div>
</body>
<script src="index.js"></script>
</html>
And here is my samples/simple/index.js :
var video,
player = null;
function init() {
video = document.querySelector('video');
player = new voplayer.Player(video);
player.load({
url: 'https://dash.akamaized.net/envivio/EnvivioDash3/manifest.mpd'
});
}
document.addEventListener('DOMContentLoaded', function () {
init();
});
html-webpack-plugin injects my library into the index.html file but I get the following error :
1beeb78b6bd26fc3d015.js:6 Uncaught TypeError: voplayer.Player is not a constructor
at init (1beeb78b6bd26fc3d015.js:6)
at HTMLDocument.<anonymous> (1beeb78b6bd26fc3d015.js:13)
(1beeb78b6bd26fc3d015.jsis referring to index.js)
This is index.html produced by html-webpack-plugin, as you can see voplayer.js is injected.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'/>
<title>voplayer.js example</title>
<!-- <script src='../../dist/voplayer.js'></script> -->
<!-- built files will be auto injected -->
<style>
video {
width: 1280px;
height: 720px;
}
</style>
<script defer src="voplayer.js"></script>
</head>
<body>
<p>Hello</p>
<div id='player' style='position: relative;'>
<video controls autoplay></video>
</div>
</body>
<script src="1beeb78b6bd26fc3d015.js"></script>
</html>
If I compare the injected voplayer.js, his size is 2.60 Mb against 660 kb for my build voplayer.js.
What am I doing wrong?
Edit
Here is my repository (without node-modules ^^)
.
├── README.md
├── dist
│ ├── df9b96316665542ddd95.js
│ ├── index.html
│ ├── voplayer.js
│ └── voplayer.js.LICENSE.txt
├── index.js
├── index.ts
├── lib
│ ├── index.d.ts
│ └── src
│ ├── AppleNativePlayer.d.ts
│ ├── DashJSPlayer.d.ts
│ ├── HlsJSPlayer.d.ts
│ ├── IPlayer.d.ts
│ ├── Player.d.ts
│ ├── TizenPlayer.d.ts
│ ├── Types.d.ts
│ ├── core
│ └── manager
├── package-lock.json
├── package.json
├── samples
│ └── simple
│ ├── index-template.html
│ └── index.js
├── src
│ ├── Player.ts
│ ├── Types.ts
│ ├── core
│ │ ├── ICorePlayer.ts
│ │ ├── apple
│ │ ├── dashjs
│ │ └── hlsjs
│ └── manager
│ ├── AdManager.ts
│ └── VOTextManager.ts
├── tsconfig.json
├── tslint.json
├── webpack
└── webpack.config.js
Thanks for reading and helping :)
I am trying to integrate PWA into this html page https://moodmap.app/ .
I have used a manifest and created the service worker required - but it won't load for some reason?
directory layout below.
└── src
├── assets
│ ├── icons.svg
│ ├── images
│ │ ├── apple-touch-icon.png
│ │ ├── favicon.png
│ │ ├── image01.jpg
│ │ ├── image02.jpg
│ │ ├── _image02.svg
│ │ └── share.jpg
│ ├── LovelyChart.custom.js
│ ├── main.css
│ ├── main.js
│ ├── main_large.css
│ ├── main.min.js
│ ├── noscript.css
│ ├── noscript_other.css
│ └── videos
│ ├── video01.mp4
│ └── video01.mp4.jpg
├── example.html
├── index.html
├── manifest.json
├── pay.html
├── serviceworker.js
├── signup.html
└── stats.html
servicework.js
const staticMoodmap = "dev-MoodMap-site-v1"
const assets = [
"/",
"/index.html",
"/stats.html",
// "/signup.html",
// "/pay.html",
// "/assets/noscript.css",
// "/assets/main.css",
"/assets/main.min.js",
"/assets/LovelyChart.custom.js",
"/assets/icons.svg",
"/assets/images/image01.jpg",
"/assets/images/image02.jpg",
"/assets/images/share.jpg",
"/assets/images/_image02.svg",
"/assets/images/favicon.png",
"/assets/images/apple-touch-icon.png",
"/assets/videos/video01.mp4.jpg",
"/assets/images/video01.mp4",
]
self.addEventListener("install", installEvent => {
installEvent.waitUntil(
caches.open(staticMoodmap).then(cache => {
cache.addAll(assets)
})
)
})
self.addEventListener("fetch", fetchEvent => {
fetchEvent.respondWith(
caches.match(fetchEvent.request).then(res => {
return res || fetch(fetchEvent.request)
})
)
})
manifest.json
{
"name": "MoodMap",
"short_name": "MoodMap",
"start_url": "./index.html",
"display": "standalone",
"background_color": "#fdfdfd",
"theme_color": "#db4938",
"orientation": "portrait-primary",
"icons": [
{
"src": "/assets/images/image01.jpg",
"type": "image/jpg", "sizes": "72x72"
}
]
}
main.js
....
if ("serviceWorker" in navigator) {
window.addEventListener("load", function() {
navigator.serviceWorker
.register("./serviceworker.js")
.then(res => console.log("service worker registered"))
.catch(err => console.log("service worker not registered", err))
})
}
So I'm thinking that it has something to do with how I am integrating the javascript? or the directory structure?
I think the problem is on setting index.html because this link is not available
https://moodmap.app/index.html
So maybe you should change
"start_url": "./index.html"
You should change it to
"start_url": "/index.html"
Or
"start_url": "/"
I'm trying to test the following function:
export async function loadLocale(language) {
return await import(`./locales/${language}.json`)
}
With the following code:
import * as utils from '#/langs/utils'
describe('language utils to interact with vue-i18n', () => {
it('should load a given locale from file', async () => {
const language = 'es'
expect.assertions(1)
const locale = await utils.loadLocale(language)
expect(locale).toEqual({ foo: 'bar' })
})
})
I have installed babel-plugin-dynamic-import-node because the dynamic imports don't work in Node and must be replaced with require.
This are the contents of the es.json I try to load:
{
"foo": "bar"
}
My .babelrc config is as follows:
{
"presets": [
"#vue/app"
],
"plugins": [
"syntax-dynamic-import"
],
"env": {
"test": {
"plugins": ["dynamic-import-node"]
}
}
}
And this is my folder structure:
│ ├── langs
│ │ ├── i18n.js
│ │ ├── locales # Actual locales
│ │ │ ├── en.json
│ │ │ └── es.json
│ │ └── utils.js
├── tests
│ └── unit
│ ├── langs
│ │ ├── locales # Mocked locales
│ │ │ ├── en.json
│ │ │ └── es.json
│ │ └── utils.spec.js
As I'm using the configuration provided by vue-cli I've tried running Jest with the --no-cache flag as this answer mentions.
The error I get is the following:
Cannot find module 'function () {
return require("./locales/".concat(language, ".json"));
}' from 'utils.js'
11 | }
12 |
> 13 | export async function loadLocale(language) {
14 | return await import(`./locales/${language}.json`)
15 | }
16 |
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:169:17)
at src/langs/utils.js:13:8
Which doesn't make sense, because ./locales/es.json exists! And moreover, i've tried using require with the exact same route from the test and it loads correctly it's contents.
Any ideas?
You use dynamic-import-node plugin twice. I did the same with
"presets": [
"react-app",
],
See vue-cli/packages/#vue/babel-preset-app/index.js
I have a gulp workflow with everything works well, but it seems like it only work on the uppermost file, for example: public/index.html, but if I would like to use public/products/item.html, gulp-data would throw an error and look for src/json/item.json instead of src/json/products/item.json.
I think there is something wrong since I used path.basename(file.path, '.jade') + '.json'), it will return the filename only, of course, if so, how to make it also provide the directory if it throws something like products/item.jade?
var basePath = {
src: 'src/',
dest: 'public/'
}
var src = {
pages: basePath.src + 'jade/',
json : basePath.src + 'json'
}
gulp.task('make:pages', function() {
return gulp.src(src.pages + '**/!(_)*.jade')
.pipe(plumber({
errorHandler: onError
}))
.pipe(data(function(file) {
return JSON.parse(fs.readFileSync('src/json/' + path.basename(file.path, '.jade') + '.json'));
}))
.pipe(jade({
pretty: true
}))
.pipe(gulp.dest(basePath.dest));
});
Providing I am using the same folder structure:
src
├── fonts
├── jade
│ ├── _layout.jade
│ ├── index.jade
│ ├── product
│ │ └── item.jade
│ ├── partials
│ │ ├── _footer.jade
│ │ └── _header.jade
├── js
├── json
│ ├── index.json
│ ├── product
│ │ └── item.json
I used this instead:
return JSON.parse(fs.readFileSync(file.path.replace(/jade/g, 'json')));
and it works! :)