How to use webpack dynamic imports for submodules? - javascript

Let's say I have a single file <google-map> component which has a template which has a <widget> component and a <marker> component.
If I dynamically import my <google-map> component it would look like this:
Vue.component(
'google-map',
() => import('#/components/maps/GoogleMapAsync.vue')
);
Now I know I will not be needing the marker or the widget components outside of the google map component.
So when my map component loads I would also like to import all of the imports that this file has, and even preferably all the way down to the bottom of that tree.
So my map, widget and marker component will all be split off into one chunk.
Is there a way to automate this in Webpack instead of continuously chaining promises or resolving promises from a Promises.all stack?
I guess one way would be to put all of the imports in the same chunk like so:
import(/* webpackChunkName: 'googlemap', '#/components/maps/GoogleMapAsync.vue')
And do this for each component I want to be part of the googlemap chunk, but this is still a lot of manual work for something that I hope can be automated.

Try this babel-plugin-syntax-dynamic-import webpack plugin that let you import using webpack chunks. Also make sure chunks are genenrated.
// Webpack
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js',
chunkFilename: '[name].js'
},
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
plugins: [require('babel-plugin-syntax-dynamic-import')]
}
}]
// Vue code
methods: {
LoadMarker() {
this.componentMarker = () => import (`./path`)
// Vue.js < 2.5 .0 // .then(m=> m.default) ;
}
}
<component :is="componentMarker"></component>

Related

Class constructor Controller canno tbe invoked without 'new' error Stimulus + webpack

I have a project where Im trying to install stimulus and stimulus tailwind components
I added webpack and a webpack config file :
// Webpack uses this to work with directories
const path = require('path');
// This is the main configuration object.
// Here, you write different options and tell Webpack what to do
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}
],
},
// Path to your entry point. From this file Webpack will begin its work
entry: './src/js/index.js',
// Path and filename of your result bundle.
// Webpack will bundle all JavaScript into this file
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '',
filename: 'bundle.js'
},
// Default mode for Webpack is production.
// Depending on mode Webpack will apply different things
// on the final bundle. For now, we don't need production's JavaScript
// minifying and other things, so let's set mode to development
mode: 'development'
};
From what I understand using babel should transpile my es6 js to browser compatible js.
Then in src/index.js I have the stimulus code :
// Start StimulusJS
import { Application } from "#hotwired/stimulus"
const application = Application.start();
// Import and register all TailwindCSS Components
import { Alert, Autosave, Dropdown, Modal, Tabs, Popover, Toggle, Slideover } from "tailwindcss-stimulus-components"
application.register('alert', Alert)
application.register('autosave', Autosave)
application.register('dropdown', Dropdown)
application.register('modal', Modal)
application.register('tabs', Tabs)
application.register('popover', Popover)
application.register('toggle', Toggle)
application.register('slideover', Slideover)
and in the html file I require my bundle.js :
<script src="../dist/bundle.js"></script>
However I keep getting this error :
Uncaught (in promise) TypeError: Class constructor Controller cannot
be invoked without 'new'
from
tailwindcss-stimulus-components.module.js:12
Here's a link to the project
Is there something wrong with my webpack setup? How can I fix this error?

multiple pages in Vue.js CLI

I'm having trouble figuring out how to have multiple pages in a Vue CLI project. Right now I have my home page with a few components and I want to create another page but I do not know how to do that. Am I supposed to create multiple html files where the index.html by default is? In a simple file structure with css js img folder and html files as pages I know that creating another html file means making another page. But I don't understand how this works with Vue CLI project.
I saw stuff like vue-router and "pages" in Vue documentation but I do not understand them very well. What are my alternatives? Is there a guide that explains that in detail, because I wasn't able to find any, let alone detailed. Would be very happy if you could help! Thank you!
First: always read the official documentation. With Vue you can build a SPA, and a MPA is also no problem. Just follow the guides:
https://cli.vuejs.org/
https://cli.vuejs.org/guide/html-and-static-assets.html#building-a-multi-page-app
https://cli.vuejs.org/config/#pages
You should create a new project with Vue CLI 3. Once you've created your project set it to be manually configured. Make sure you don't choose the SPA option. Vue will then create a nice "start" project using a MPA approach. After that, just repeat the config on vue.config.js.
Updated #1
It seems that some updates on Vue Cli, have changed the way to build a MPA app, so:
Create a new application vue create test
Choose Manual configuration
The boilerplate created will be for a SPA. So make the following changes:
Create a folder under src named pages (optional)
Into this folder create your own pages: Home, About, etc.
Copy and paste the App.vue and main.js from src, into your new folders - Home, etc.
Format the App.vue into this folders, to your liking.
Create a vue.config.js and set it like this: https://cli.vuejs.org/config/#pages
Below, I have three images demonstrating this:
First: a fresh new app
Second: this same app, with the changes I made above
Third: the vue.config.js from this app
You don't need to create the pages folder, this is just to get the idea.
Link to GitHub: Building a MPA App
EDIT: Vue has this built-in. Skip to the bottom for more.
Original answer:
There are two ways to interpret your question, and therefore to answer it.
The first interpretation is: "how can I support routing to different pages within the same single-page app, e.g. localhost:8080/about and localhost:8080/report etc?". The answer to this is to use the router. It's reasonably straightforward and works well.
The second interpretation is: "my app is complex, and I have multiple single-page applications, e.g. one app for the 'website' part, one app for consumers to log in and do work, one app for admins, etc - how can vue do this, without making three entirely separate repositories?"
The answer to the latter is a single repository with multiple single-page apps. This demo looks like exactly what you're after:
https://github.com/Plortinus/vue-multiple-pages/
Look in particular at: https://github.com/Plortinus/vue-multiple-pages/blob/master/vue.config.js
Updated answer:
It turns out that vuejs has the idea of multiple top-level pages built-in. I mean, it makes sense - it's going to be really common, despite what many incorrect answers are saying about "no, it's for single page apps"!
You want the pages option in the vue.config.js file:
https://cli.vuejs.org/config/#pages
If your project doesn't have that file in the root directory, create it and vuejs will discover it.
There is a long and a short way to define each page. I used the short form here:
module.exports = {
pages: {
index: 'src/pages/index/main.ts',
experiment: 'src/pages/experiment/main.ts'
}
}
You don't have to put your work under "pages". It could be "/src/apps/index/index.ts" or whatever.
After moving code around and changing some imports from:
import HelloWorld from './components/HelloWorld'
to
import HelloWorld from '#/components/HelloWorld'
The app works - but the "experiment" app in my repo had to be loaded like this:
http://localhost:8080/experiment.html
Pretty ugly, and even worse because it uses the router which resulted in URLs like:
http://localhost:8080/experiment.html/about
Ugh.
Fortunately, this stackoverflow answer solved it. Update the vue.config.js file to include devServer options (make sure this is at the top level of the exported object:
devServer: {
historyApiFallback: {
rewrites: [
{ from: /\/index/, to: '/index.html' },
{ from: /\/experiment/, to: '/experiment.html' }
]
}
}
Then also modify the router.ts file to append the extra path (in my case "experiment/":
export default new Router({
mode: 'history',
base: process.env.BASE_URL + 'experiment/',
...
Then URLs resolve nicely, e.g.: http://localhost:8080/experiment/about
This may not be relevant to the question, but bear with me, maybe my answer can help someone.
I use webpack+vue, and I have figured out how to build multiple pages applications. Here my webpack.config.js:
const path = require('path');
const fs = require('fs')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry: {
app: './src/app.js',
mgmt: ['./src/modules/mgmt/mgmt.js'],
login: './src/modules/login/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
// publicPath: '/ahezime/',
filename: (chunkData) => {
console.log('chuckData.chunk.name => ', chunkData.chunk.name)
return chunkData.chunk.name === 'app' ? './[name].bundle.js' : './[name]/[name].bundle.js';
}
},
optimization: {
minimizer: [
new TerserPlugin(),
new OptimizeCSSAssetsPlugin({})
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new CleanWebpackPlugin(['dist']),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: 'app',
template: './src/app.html',
// inject: false,
chunks: ['app'],
filename: './index.html'
}),
new HtmlWebpackPlugin({
title: 'mgmt',
template: './src/modules/mgmt/mgmt.html',
// inject: false,
chunks: ['mgmt'],
filename: './mgmt/index.html'
}),
new HtmlWebpackPlugin({
title: 'login',
template: './src/modules/login/login.html',
// inject: false,
chunks: ['login'],
filename: './login/index.html'
})
],
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: ['#babel/plugin-proposal-object-rest-spread']
}
}
}
],
rules: [
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.scss?$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
]
}
};
And here's my directory structure:
https://i.stack.imgur.com/uFvKx.png
And you can jump pages:
<template>
<div>
<h1>App</h1>
<div>
Please click me, and let take you into the login page!!!
</div>
<span>Before computed: {{ message }} </span>
<br>
<span>Afer computed: {{ computedMessage() }} </span>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello World!'
}
},
computed: {
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
},
methods: {
computedMessage: function() {
return this.message.split('').reverse().join('')
}
}
}
</script>
Note pointing users to what should be the accepted answer
At the moment of posting my initial answer I wasn't aware of the possibility of actually building MPAs in VueJS. My answer doesn't address the question asked therefore I will recommend to take a look at the answer provided by PJ.Wanderson bellow which should be the accepted answer
Inital Answer
Vue.js projects are SPAs(single page applications). You only have one .html file in the entire project which is the index.html file you mentioned. The "pages" you want to create, in vue.js are referred to as components. They will be plugged into the index.html file and rendered in the browser. A vue.js component comprises 3 parts:
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>
Template: it contains all the html your page should display (this is where you put the html of your pages)
Script: it contains all JavaScript code that will be executed on the page/component
Style: it contains the CSS that will style that specific component/page
You can check this tutorial out for a quick-start Vue.js 2 Quickstart Tutorial 2017
It explains vue.js project structure and how the various files relate to each other

Using CSS in Webpack

I've inherited a web app that uses webpack. In my app, I have a directory called "pub", which looks like this:
./pub
/styles
app.css
/images
brand.png
I have been trying unsuccessfully all morning to use these via webpack. In my webpack.config.js file, I have the following:
const path = require('path');
const projectRoot = path.resolve(__dirname, '../');
module.exports = {
entry: {
app: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
}
]
}
};
Then, in my index.js file, I have the following:
import logoImage from './public/images/brand.png';
require("css!./public/css/app.css");
When I run webpack, I receive an error that says:
BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.
You need to specify 'css-loader' instead of 'css',
see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed
I don't really understand this error. When I look at it, and then I look at my webpack.config.js file, it looks to me like I'm using css-loader. Beyond that though, how do I use a style in my webpage once the require statement is working. I'm just trying to use webpack with a web app and want to import my brand and CSS and I can't figure it out.
You don't need the css! in your require statement
require("css!./public/css/app.css");
You can just use
require("./public/css/app.css");
Because you are testing files with:
{
test: /\.css$/, // <-- here
loader: "style-loader!css-loader"
},
Or without the rule in your webpack config
// No test in rules matched but you tell webpack
// explicitly to use the css loader
require("style-loader!css-loader!./public/css/app.css");
Your hierarchy is pub/styles/app.css but the location you use in your require is public/css/app.css. It looks like you're trying to call your css from the wrong location.
If this doesn't solve your issue, check out this link https://webpack.github.io/docs/stylesheets.html
The first step on that page is to install css-loader and configure it, this might be a good place to start.

Use Babel Transform inside of React code

I'm trying to convert a string to valid JSX code, and then inject it into my React component.
const babel = require('babel-core')
let result = eval(babel.transform('<p className="greeting">Hello</p>').code)
But am getting hit with a wall of errors, because I'm trying to use Babel in the browser:
ERROR in ./~/babel-core/lib/api/node.js
Module not found: Error: Can't resolve 'fs' in '/Users/ben/Desktop/Work/code/ru-coding-challege/node_modules/babel-core/lib/api'
# ./~/babel-core/lib/api/node.js 72:10-23
# ./~/babel-core/index.js
# ./src/js/containers/app-container.js
# ./src/js/index.js
# multi (webpack)-dev-server/client?http://localhost:3000 ./src/js/index.js
...more similar errors
Yes, I know creating code from a string and injecting it is frowned upon, but D3 creates elements dynamically (i.e. you can't write them declaratively) For example: axes whose values and number of ticks change based on the data. I've successfully changed the D3 axes into JSX with htmltojsx but that returns a String. I need to turn that String into valid JSX components that I can inject into my code.
EDIT: As Michael Lyons states below, I could just use dangerouslySetInnerHTML, but I am trying to avoid this option unless everything else doesn't work. Trying to stay within the React paradigm as much as possible.
Here's how my component's render method would look:
<svg width='100%' height='600'>
<g transform='translate(50, 50)'>
<path d='...' className='path-0'></path>
</g>
{/* Insert JSX elements here. e.g. axes below */}
{axes}
</svg>
And here is my webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './src/js/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].bundle.js',
},
devServer: {
inline: true,
contentBase: path.join(__dirname, 'dist'),
port: 3000
},
plugins: [
new CopyWebpackPlugin([
{ from: 'src/html/index.html' }
]),
new webpack.DefinePlugin({
IN_BROWSER: true,
}),
],
module: {
loaders: [
{
test: /\.scss$/,
exclude: /(node_modules)/,
loader: 'style-loader!css-loader!sass-loader'
},
{
test: /\.js$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
}
}
Instead of translating HTML to JSX, then rendering with babel in your code while on the client, you could render that html directly through your React component.
Facebook's DOM element implementation has built in functionality for this use-case, and you are right it is generally frowned upon for security reasons because it opens up vulnerabilities to cross-site scripting.
Facebook has even labeled it "dangerouslySetInnerHTML" to remind devs that this is dangerous.
So if you have HTML in a string format, you can render that in JSX in a manner such as this:
getMarkup() {
return { __html: '<p class="greeting">Hello</p>' }
}
render() {
return <div dangerouslySetInnerHTML={this.getMarkup()} />;
}
This comes straight from the React DOM elements documentation here: Docs
This method should also allow you to bypass having to convert your d3 output to JSX
Edit: Introductory sentence

React is expected to be globally available

I'm playing with React (#13.3) with babel and webpack.
I have a component that's defined like this:
import BaseComponent from './BaseComponent';
export default class SomeComponent extends BaseComponent {
render() {
return (
<div>
<img src="http://placekitten.com/g/900/600"/>
</div>
);
}
}
But I get the following error:
Uncaught ReferenceError: React is not defined
I understand the error: the JSX bit is compiled into React.createElement(...) but React isn't in the current scope since it's not imported.
My questions is:
What's the clean way to work around this issue? Do I have to somehow expose React globally with webpack?
Solution used:
I followed #salehen-rahman suggestion.
In my webpack.config.js:
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'react-hot!babel?plugins[]=react-require'
}, {
test: /\.css$/,
loader: 'style!css!autoprefixer?browsers=last 2 versions'
}]
},
I also needed to fix my tests, so I added this to the file helper.js:
require('babel-core/register')({
ignore: /node_modules/,
plugins: ['react-require'],
extensions: ['.js']
});
My tests are then launched with the following command:
mocha --require ./test/helper.js 'test/**/*.js'
My questions is : What's the clean way to work around this issue ? Do I have to somehow expose React globally with webpack ?
Add babel-plugin-react-require to your project, and then amend your webpack's Babel config to have settings akin to:
loaders: [
{
test: /.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
stage: 0,
optional: ['runtime'],
plugins: [
'react-require' // <-- THIS IS YOUR AMENDMENT
]
},
}
]
Now, once you've applied the configuration update, you can initialize React components without manually importing React.
React.render(
<div>Yippee! No <code>import React</code>!</div>,
document.body // <-- Only for demonstration! Do not use document.body!
);
Bear in mind though, babel-plugin-react-require transforms your code to automatically include React imports only in the presence of JSX tag in a specific file, for a specific file. For every other file that don't use JSX, but needs React for whatever reason, you will have to manually import React.
If you have react in your node modules directory you can add import React from 'react'; at the top of your file.
You can use Webpack's ProvidePlugin. To use, update the plugins section in your Webpack config to include the following:
plugins: [
new webpack.ProvidePlugin({
'React': 'react'
})
]
This, however, doesn't solve it for the tests..

Categories

Resources