Unable to use webpack dynamic import because of static publicPath - dynamic

I love the ability to dynamically import a chunk using the import command in webpack 3. Unfortunately it seems as if it can only be used if the resources are in a rather static directory configuration on the server.
In my environment the html pages generated dynamically in the backend (let's say http:/localhost/app). All other resources (js, css, images, etc.) are in a different directory (let's say http:/localhost/res) but additionally res is not static but can rather change dynamically in the backend.
As long as I do not use any dynamic imports everything works as expected but when trying to dynamically load any chunks this fails because webpack by default expects the chunks to be in http:/localhost/app and I cannot use publicPath because the url where the resources are is dynamic.
Is there any way to (runtime) configure webpack into loading the resources relative to the path where the current resource is located.
If for example the chunk page1.js located in http:/localhost/resA dynamically loads the chunk sub1.js he should look for it in http:/localhost/resA instead of http:/localhost/app.
generated html will be available at http:/localhost/app/page1:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="http:/localhost/resA/page1.bundle.js"></script>
</body>
</html>
file src/page1.js will be available as http:/localhost/resA/page1.bundle.js:
import(/* webpackChunkName: "sub1" */ './sub1').then(({MyClass}) => {/*...*/});
file src/sub1 will be available as http:/localhost/resA/sub1.bundle.js:
export class MyClass {};
file `webpack.config.js':
const path = require('path');
const webpack = require('webpack');
function config(env) {
return {
entry: {
index: ['./src/page1.js']
},
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: './'
},
module: {
rules: [
{
test: /\.js$/i,
include: /src/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
devtool: 'source-map'
};
}
module.exports = config;

The solution is to use __webpack_public_path__ as described in https://webpack.js.org/guides/public-path.

Related

How to use PHP with Tailwind CLI?

I am using the Tailwind CLI installation and all the files work except the PHP files.
So I decided to try adding in my TailwindCSS configuration the .php extension and then tried to connect to the page with the Live Server but no the page was not using Tailwind.
Here is my tailwind.config.js:
/** #type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,php}"],
theme: {
extend: {},
},
plugins: [],
}
Ok so i manage to make it work if your .php file is located on the root directory your tailwind config must look like this.
/** #type {import('tailwindcss').Config} */
module.exports = {
content: ["./*.{html,php}"],
theme: {
extend: {},
},
plugins: [],
}
then on the portion where you have to link your output.css on your html head section you must include the ./
<link href="./dist/output.css" rel="stylesheet">
Or to validate open your dev tool network tab and check if output.css returns http response 200

How to enable vue lazy loading when the app is served from a CDN?

I am working on a project with an isolated frontend and backend. The backend is a Laravel app serving the APIs and the frontend is a VueJS app consuming those APIs.
The backend app is deployed on a digitalocean droplet, and the frontend app is deployed on netlify. However, the end-users do not hit the Vue app. The Laravel app has an env variable called ASSET_URL where I've assigned the netlify URL. I've then added the script and link tags pointing to the resources (/js, /css) of /dist directory of the Vue app to my main Laravel view file that gets returned from the Laravel app:
// app.blade.php
...
<link rel="stylesheet" href="{{config('app.asset_url') . '/css/app.css'}}">
...
<script src="{{config('app.asset_url') . '/js/app.js'}}"></script>
<script src="{{config('app.asset_url') . '/js/app.chunk.js'}}"></script>
...
In the Vue app, I have overridden the default webpack configuration in the vue.config.js file so that webpack does not insert the [contentHash] in the filenames which makes it easier to inject the static resources into the Laravel app:
// vue.config.js
module.exports = {
devServer: {
host: "0.0.0.0",
disableHostCheck: true,
port: '8080'
},
chainWebpack: config => {
if (config.plugins.has('extract-css')) {
const extractCSSPlugin = config.plugin('extract-css')
extractCSSPlugin && extractCSSPlugin.tap(() => [{
filename: 'css/app.css',
chunkFilename: 'css/app.chunk.css'
}])
}
},
configureWebpack: {
optimization: {
splitChunks: false
},
output: {
filename: 'js/app.js',
chunkFilename: 'js/app.chunk.js'
}
}
}
Long story short, from the end-users perspective, the Vue app does not exist.
But the problem is, now I have lost the ability to lazy load the routes, because if I use the /* webpackChunkname: "product" */ magic comment in my route definition like this:
component: () => import(/* webpackChunkName: "product" */ "#/views/product/ProductList.vue")
Then Webpack will throw an error:
conflict: Multiple chunks emit assets to the same filename (app.chunk.js)
So I've gone ahead and made the following changes to my vue.config.js file:
module.exports = {
devServer: {
host: "0.0.0.0",
disableHostCheck: true,
port: '8080'
},
chainWebpack: config => {
if (config.plugins.has('extract-css')) {
const extractCSSPlugin = config.plugin('extract-css')
extractCSSPlugin && extractCSSPlugin.tap(() => [{
filename: 'css/app.css',
chunkFilename: 'css/[name].css'
}])
}
},
configureWebpack: {
output: {
filename: 'js/app.js',
chunkFilename: 'js/[name].js'
}
}
}
This is what the build files look like:
dist/js/chunk-vendors.js 2083.74 KiB 607.00 KiB
dist/js/app.js 1548.29 KiB 259.22 KiB
dist/js/chunk-b0a634c6.js 287.19 KiB 96.31 KiB
dist/js/product.js 7.51 KiB 2.43 KiB
dist/precache-manifest.78cd95684ece42546415aacd7f68cced.js 2.43 KiB 0.86 KiB
dist/service-worker.js 1.04 KiB 0.61 KiB
dist/css/app.css 419.79 KiB 60.69 KiB
dist/css/chunk-vendors.css
And I am injecting the resources to my laravel app like this:
<head>
<link rel="stylesheet" href="{{config('app.asset_url') . '/css/app.css'}}">
<link rel="stylesheet" href="{{config('app.asset_url') . '/css/chunk-vendors.css'}}">
</head>
…
<script src="{{config('app.asset_url') . '/js/app.js'}}"></script>
<script src="{{config('app.asset_url') . '/js/chunk-vendors.js'}}"></script>
<script src="{{config('app.asset_url') . '/js/chunk-b0a634c6.js'}}"></script>
<script src="{{config('app.asset_url') . '/js/product.js'}}"></script>
To test this set up locally, my laravel app is being served from
test.myapp.test, and the vue app is being served from localhost:8080. So I have assigned http://localhost:8080 to the ASSET_URL.
I have two problems now:
On initial load, all the the resources including product.js are loading even when I am not on that route. That is because I've hard-coded the script/link tags. And it defeats the purpose of lazy loading (load only when I'm on that route)
When I visit the /product route, the application does look for the product.js file lazily, but instead of looking for http://localhost/8080/js/product.js, it is looking for http://test.myapp.test/js/product.js, so I end up getting the following error:
vue-router.esm.js?8c4f:2257 ChunkLoadError: Loading chunk product failed.
(missing: http://test.myapp.test/js/product.js)
How do I lazy load routes or what modifications do I need in my webpack config so that the lazy loaded components are searched in the ASSET_URL instead of the current host?
[Even if you have a better idea to lazy load routes when using a CDN, it would be extremely helpful if you please share it]
I recommend you to take a look on the below package
https://github.com/hilongjw/vue-lazyload
OR
https://www.npmjs.com/package/vue-clazy-load
wherein for images all you need to do is pass the local/cloud path and the rest is handled by the package itself

how to prevent mini-css-extract-plugin from creating a js entrypint

I am relatively new to express + webpack, so i am unclear wether this is intended or not, and if not, how to properly configure it. the question is around the additional asset & entry point created when using the mini-css-extract-plugin.
webpack config:
Extract = require('mini-css-extract-plugin');
path = require('path');
Write = require('write-file-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
demo_scripts: path.resolve('server', 'scripts', 'demo.js'),
demo_styles: path.resolve('server', 'styles', 'demo.css')
},
output: {
path: path.resolve('.tmp'),
filename: '[name].js'
},
plugins: [new Write(), new Extract()],
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
]
},
{
test: /\.css/,
use: [
{
loader: Extract.loader
},
{
loader: 'css-loader'
}
]
}
]
}
};
webpack output
Asset Size Chunks Chunk Names
demo_scripts.js 3.91 KiB demo_scripts [emitted] demo_scripts
demo_styles.css 36 bytes demo_styles [emitted] demo_styles
demo_styles.js 3.89 KiB demo_styles [emitted] demo_styles
Entrypoint demo_scripts = demo_scripts.js
Entrypoint demo_styles = demo_styles.css demo_styles.js
my question is, why is demo_styles.js being created? although the css is being extracted, it almost seems like webpack is still creating a bundled js with css, but when i view that file, the only line in it is
eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./server/styles/demo.css?");
can anyone help explain what is going on here?
UPDATE
if i remove the demo_styles entry point, and configure it via the plugin init, no css asset is built.
({
plugins: [
new Write(),
new Extract({
filename: 'demo_styles.css'
})
]
});
Asset Size Chunks Chunk Names
demo_scripts.js 3.91 KiB demo_scripts [emitted] demo_scripts
Entrypoint demo_scripts = demo_scripts.js
the repo for this is here (note the express branch) https://github.com/brewster1134/bumper/tree/express
There are two workarounds for your problem. For both of them, you need to change the entry point of the Webpack configuration file. I, personally, prefer the first option.
Option 1:
Change the entry to the following:
entry: {
demo: [
path.resolve('server', 'scripts', 'demo.js'),
path.resolve('server', 'styles', 'demo.css'),
]
}
This will generate the following outputs (based on the filename you provided for Extract class and output section:
demo.js
demo_styles.css
Option 2:
For this option, you need to remove the CSS file from the entry point and import it inside the JS file:
webpack.config.js
...
entry: path.resolve('server', 'scripts', 'demo.js'),
...
demo.js
import './../styles.demo.css'
//rest of your JS codes
This solution will generate the same output as Option1
Webpack pulls everything into a js file, then MiniCssExtractPlugin takes it out of that file, leaving a blank js file with // extracted by mini-css-extract-plugin.
My solution is to group your css and js in the entry section of webpack.config.js
entry: {
demo: {
import: [ path.join("server", "scripts", "demo.js"), path.join("server", "styles", "demo.css") ],
filename: "demo.js", // outputs demo.js, demo.css to your output directory
},
main: {
import: [ path.join("server", "scripts", "main.js"), path.join("server", "styles", "main.css") ],
filename: "main.js", // outputs main.js, main.css to your output directory
},
}
Also, so naming works well, use this for your plugins section:
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css"
}),
],
Adjust the bundles "demo" and "main", as well as paths accordingly.
Please remove demo_styles from your entry point this is creating demo_styles.js.
instead you can inject css file like this:
plugins: [
new MiniCssExtractPlugin({
filename: 'demo_styles.css',
}),
Let me know if the issue still persists, Happy to help

Can I use a Preact component via Custom Elements?

I'd like to create a Preact component and let folks consume it, even if they're not building Preact apps.
Example: I'd like to build a <MyTooltip> component in Preact, bundle it (along with the Preact runtime), and have folks load it as a script tag use it purely declaratively, perhaps like:
<script src="https://unpkg.com/my-tooltip/my-tooltip-bundled.js">
<my-tooltip content="Tooltip content">Hover here</my-tooltip>
Is there a way to bundle up a component such that it includes the Preact runtime, my library code, and hooks into <my-tooltip> elements?
In other words, can I interop my Preact components as Custom Elements, similar to ReactiveElements?
There's a great library that does this for you called preact-custom-element:
https://github.com/bspaulding/preact-custom-element
class SearchBox extends Component {
render() {
// ...
}
}
registerComponent(SearchBox, 'search-box');
Even though #Jason Miller`s answer helped me a lot, I still had difficulties in producing a basic working example, so here is how I solved this problem from start to finish:
My basic html document including the bundled script dummy.js containing the actual code of my dummy webcomponent:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<script async src="./dummy.js" type="text/javascript"></script>
<dummy-view name="Test"></dummy-view>
</div>
</body>
</html>
My dummy webcomponent:
import {div} from '../utils/hyperscript';
import registerCustomElement from 'preact-custom-element';
const DummyView = ({ name = "World" }) => (
div({}, `Hello, ${name}!`)
);
registerCustomElement(DummyView, "dummy-view", ["name"]);
My webpack config:
const path = require('path');
module.exports = {
entry: {
dummy: './lib/dummy/dummy-view.js'
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
output: {
path: path.resolve(__dirname, 'webcomponent/')
}
};
Some more details:
I installed the preact-custom-element like so npm i preact-custom-element.
Bundling is done using webpack like so: npx webpack.
The index.html will be served under /webcomponent (e.g. http://localhost:3000/webcomponent/).
When visiting the above URL in the browser, the result will look like this:
<div>Hello, Test!</div>
Addendum:
Since I'm using preact I found an alternative approach using preact-habitat, which does something very similar: https://github.com/zouhir/preact-habitat
PreactJS themselves have a library to do just that
https://github.com/preactjs/preact-custom-element
In case you don't want to use another library to keep it slim you can do:
import {h} from "preact";
h("lottie-player", {
src: "/lf30_editor_mjfhn8gt.json",
background: "transparent",
speed: 1,
style: {
width: 300,
height: 300,
},
loop: true,
autoplay: true,
})
I wrote an article on achieving just this:
https://www.jameshill.dev/articles/custom-elements-with-preact/
In addition to other packages mentioned already, there's also:
https://github.com/jahilldev/preactement
It works in a similar way, but allows you to dynamically import your component files when needed, reducing your upfront javascript:
import { define } from 'preactement';
define('my-tooltip', () => import('./myTooltip'));
Disclosure: I'm the author :)

Load customized Dojo module

I am really new to Dojo so this may sound dumb.
I am using Dojo 1.7 as a hosted resource (that is, I downloaded the dojo package and put it under the source code). Then I have a customized module defined in another folder. The structure looks like this:
/
libs/
js/
dojo/
dojo.js
myPage/
myModules/
myCustomizedModule.js
index.html
I am using the "define" function to define a module in myPage/myModules/myCustomizedModule.js
In "myPage" folder, I am using index.html to require the customized module:
<script>
require(["myPage/myModules/myCustomizedModule"], function(myCustomizedModule){
// Do something.
})
</script>
However, I can't get it to work: the console reported an error:
"http://localhost/myDojoTest/libs/js/dojo/myPage/MyModules/myCustomizedModule.js 404 (Not found)".
I know this directory is not right since "myPage" folder is not under "libs/js/dojo". But it seems when using the "require" statement, instead of using the relative path of the current HTML document, the code uses the current path for the dojo.js file.
Is there anything I can do to correctly refer to my customized module?
Many thanks!
As per your requirement, you need to set up packages as indicated below
<!-- dojo configuration options -->
<!-- For Package configuration refer tutorial at http://dojotoolkit.org/documentation/tutorials/1.7/modules/ -->
<script type="text/javascript">
var dojoConfig = {
async: true,
baseUrl: "/",
tlmSiblingOfDojo: false,
packages: [
{ name: "dojo", location: "libs/js/dojo" },
{ name: "dijit", location: "libs/js/dijit" },
{ name: "dojox", location: "libs/js/dojox" },
{ name: "myModules", location: "myPage/myModules" }
]
};
</script>
You can than access dojo, dijit and myModules in require function call as.
Remember you need to precede the modules with their respective packages.
<script>
require([
//Require resources.
"dojo/store/Memory",
"myModules/myCustomizedModule"
], function(Memory, myCustomizedModule){
....
}
);
</script>
Hope it helps.