What I'm trying to achieve is:
Building simple react app - the template is create react app
Copying output file (main.*.js)
Pasting it in another react app
Importing render function to render the first app into the second one
Simple react app code:
interface Props {
greeting: string;
}
export module AppModule {
export const sendGreetings = ({ greeting }: Props) => {
return `Hello ${greeting}`;
};
}
Builder file code:
!function(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}}();
Trying to import this file into another app I get this error:
File 'c:/vscode/test-react-app/test-sc-react/src/main.783e0281.js' is not a module.ts(2306)
Which is obvious. I changed the output file manually to:
export function initApp(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}};
It works but the only function that I'm able to access is initApp but not sendGreetings
I've been struggling with this for a while now and I would really appreciate any helpful suggestions
I used Bit.dev for my components that are used across multiple applications & there is an article regarding your issue
https://blog.bitsrc.io/sharing-react-components-across-multiple-applications-a407b5a15186
I think it would help.
🎯 Solution #1
You can use an iframe to inject your react app:
<iframe src='path-to-your-app.html'/>
🎯 Solution #2
Go with micro-frontend architecture approach. Where a front-end app is decomposed into individual, semi-independent "microapps" working loosely together.
As a starting point, you can try npx create-mf-app instead of the CRA.
You can include your js code directly on run time. You can use window.addEventListener to load js/css incoming from an outside source. You just have to append that js to your document on the load event.
I'm trying to figure out how I can bundle some Components which use the same data (from an API) so I only need to make one call. I'm learning VueJS right now and I don't know if this is even possible or how it is called. So basicly what I'm trying to archive is this:
Create an index.js file which get's the data from the API and bundles the components which use this data (or parts of it).
Useing import {subComponent} from '#/components/bundle/' to get a specific component from that bundle while it uses the data delivered by index.js
Is this possible? Probably this is common but I don't know what it's called. How can I realize this?
So far I tried this:
view.vue:
<template>
<subComponent1/>
</template>
<script>
import {subComponent1} from '#/components/bundle';
export default {
name: 'view',
components: {
subComponent1
},
</script>
index.js (in components/bundle/):
<script>
import subComponent1 from './subComponent1'
import subComponent2 from './subComponent2'
export {
subComponent1 ,
subComponent2
}
</script>
The Components are standart single-file components and don't use data from index.js yet, but I'm already getting this error:
What am I doing wrong?
Let's say I have a normal react application using redux and some ajax calls.
If I want to pass it to someone I will give them the bundled js file I created with webpack and ask them to include it in their HTML + render a div with an id of "myApp" for example:
<div id="myApp"></div>
Ok, what if their website is also created with react, and they want to include my bundled js file inside one of their components, and of course render the relevant div?
I tried to use import or require to simulate this:
require('./path/to/myBundle.js');
import './path/to/myBundle.js';
Example:
//...
import './path/to/myBundle.js'; // the file that will render myApp to the relevant div
// ....
export function SomeApp(args){
return(
<div>
<div id="myApp"></div>
<SomeComponent />
</div>
);
};`
This does not work as I get some errors about:
Uncaught Error: Minified React error #37; visit
http://facebook.github.io/react/docs/error-decoder.html?invariant=37
for the full message or use the non-minified dev environment for full
errors and additional helpful warnings.
And when I visit this site I see:
_registerComponent(...): Target container is not a DOM element.
However, if they'll use this file (myBundle.js) outside their components (top level index.html for example) it will work just fine of course.
EDIT:
I forgot to mention that I think I know what the problem is, the application doesn't have the HTML ready with this div yet. but I don't know a good and native way to wait for it to exist.
EDIT #2 following #Frxstrem 's answer:
I'm trying to follow this answer but I think I'm doing it wrong.
I have 2 copies of corry house slingshot demo app as app1 and app2.
changed the 'output' on webpack.config.prod.js of app1 to:
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: 'app1Bundle.js',
library: "App1",
libraryTarget: "umd"
},
I'm trying to render app1 inside the homepage component of app2.
so i copied the "published" files from app1 to the root of app2 and called the folder app1, then added an import call:
import {app1} from '../../app1/app1Bundle';
and a matching tag inside the return function:
const HomePage = () => {
return (
<div>
<app1 />
<h1>App 2</h1>
</div>
);
};
I get the same error as I posted above.
I also tried different combinations:
import app1 from '../../app1/app1Bundle'; // without curly braces
or even just getting the script as a normal js script
import '../../app1/app1Bundle';
or
require('../../app1/app1Bundle');
and then tried to render a normal div tag with an id of "app1"
const HomePage = () => {
return (
<div>
<div id="app1"></div>
<h1>App 2</h1>
</div>
);
};
nothing seems to work as I still get the same error.
I think the problem is the timing of the script load and the rendering of the elements. I think the div does not exist yet when the bundled script is searching for it.
By default, Webpack will expose the entry module as a variable, which is useful when you include scripts with a <script> tag. (Because of this, if you require it you would likely just get {}.) However, if you want to load your bundle from other modules, you'll need to tell Webpack to expose it as an exported module instead.
The easiest way to do this is to set
{
...
"libraryTarget": "umd"
}
in your Webpack configuration. With that, Webpack knows that it should expose your entry module as a module that can be required in Webpack, but can also be loaded with a <script> tag as necessary.
Webpack libraryTarget documentation
The main problem i faced was to include the bundled js file of app1 after the DOM contains the target div it needs.
What i ended up doing was, creating a component in app2 project that will require() the bundled js file on componentDidMount() and will render and return the target div with a relevant id.
The reason i created a component is purely for re-usability purpose, instead of requiring this script with componentDidMount() on every component that needs it.
So, this is the component:
import React from 'react';
class AppOne extends React.Component {
componentDidMount() {
require('../app1/app1Bundle.js');
}
render() {
return (
<div id="app1"></div>
);
}
}
export default AppOne;
And this is how i use it in other component:
import React from 'react';
import AppOne from './AppOne';
const HomePage = () => {
return (
<div>
<h1>App 2 - wrapper for app1</h1>
<hr />
<AppOne />
<hr />
<h1>This is App2 as well </h1>
</div>
);
};
export default HomePage;
It's working fine. my only concern is that i may face some conflicts with react because i'm using 2 react apps though for ow i don't see any errors.
I guess that's an issue for a different question.
EDIT:
If someone will use this approach you should note that this will work only for the first load. because after the component will re-render itself the bundled script will not run again.
I have made components and saved into different '.vue' files and compiling them with the help of elixir / gulp and of course browserify.
I am including all the components into one js file,
I want to know that how to reduce the size of "build.js" file that is called each time in every page that contains all the components of the app.
It would be helpful to know such a way to include components only when they required.
This question is not related to Vue-router
I am new to vue.js
Here is your answer: http://router.vuejs.org/en/lazy.html
This is supported natively in Webpack. The example is:
require(['./MyComponent.vue'], function (MyComponent) {
// code here runs after MyComponent.vue is asynchronously loaded.
})
If you want to do it with Broswerify, you'll need https://github.com/substack/browserify-handbook/blob/master/readme.markdown#partition-bundle . The code looks like:
router.map({
'/async': {
component: function (resolve) {
loadjs(['./MyComponent.vue'], resolve)
}
}
})
I'm new to Vue.js, I've used AngularJS for some time and in angular we used to load templates such as,
template: '/sometemplate.html',
controller: 'someCtrl'
How can we do such a thing in Vue, instead of keeping large HTML templates inside JavaScript like this,
new Vue({
el: '#replace',
template: '<p>replaced</p>'
})
This is OK for small templates but for large templates is this practical?
Is there a way to load external template HTML or use HTML template inside a script tag like in Vue?
<script type="x-template" id="template">HTML template goes here</html>
You can use the script tag template by just referring to its id.
{
template: '#some-id'
}
Though, I highly recommend using vueify (if you use browserify) or vue-loader (if you use webpack) so you can have your components stored in nice little .vue files like this.
Also, the author of Vue wrote a nice post about the topic of external template urls:
https://vuejs.org/2015/10/28/why-no-template-url/
You can try this:
for Vue2 : https://github.com/FranckFreiburger/http-vue-loader
for Vue3 : https://github.com/FranckFreiburger/vue3-sfc-loader
Example (Vue2) :
new Vue({
components: {
'my-component': httpVueLoader('my-component.vue')
},
...
Example (Vue3) :
Vue.createApp({
components: {
'my-component': Vue.defineAsyncComponent(() => loadModule('./myComponent.vue', opts))
},
...
David, that is a nice example, but what's the best way to make sure the DOM is compiled?
https://jsfiddle.net/q7xcbuxd/35/
When I simulate an async operation, like in the example above, it works. But as soon as I load an external page "on the fly", Vue complains because the DOM is not ready.
More specifically:
Uncaught TypeError: Cannot set property 'vue' of undefined
Is there a better way to do this than to call $compile when the page has loaded? I've tried with $mount, but that didn't help.
UPDATE:
Never mind, I finally figured out how to do it:
Vue.component('async-component', function (resolve, reject) {
vue.$http.get('async-component.html', function(data, status, request){
var parser = new DOMParser();
var doc = parser.parseFromString(data, "text/html");
resolve({
template: doc
});
});
});
And in the actual template, I removed the
<script id="someTemplate" type="text/x-template"></script>
tags and only included the html.
(This solution requires the http loader from https://cdnjs.cloudflare.com/ajax/libs/vue-resource/0.1.10/vue-resource.min.js)
I've tried http-vue-loader and it works fine.
This library is easy to use and has good documentation and examples
Although you can't load templates from filed directly you still can keep html in separate single-file components. You can even skip <script>...</script> part.
Usage (from loader's documentation)
my-component.vue
<template>
<div class="hello">Hello {{who}}</div>
</template>
index.html
<!doctype html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
</head>
<body>
<div id="my-app">
<my-component></my-component>
</div>
<script type="text/javascript">
new Vue({
el: '#my-app',
components: {
'my-component': httpVueLoader('my-component.vue')
}
});
</script>
</body>
</html>
Both files should be placed in one folder at the same level
1. In Vue 2.x I recommend sticking with convention by using .vue files but instead, inverting the order of imports:
// template.vue
<template>
<div class="helloworld">
<h1>Hello world</h1>
</div>
</template>
<script>
import src from './src'
export default src
</script>
and in a separate file
// src.js
export default {
name: 'helloworld',
props: {},
...
}
Then in your component registration
import helloworld from './helloworld/template.vue'
new Vue({
components: {
'helloworld': helloworld
},
...})
This way you get the best of both worlds and you don't have to force yourself to build templates within a string.
2. If you want to lazy load, apparently there is a way to do so in Vue 2.x
new Vue({
components: {
'helloworld': () => import(/* webpackChunkName: "helloworld" */ './helloworld/template.vue')
},
...})
This will load helloworld.js (which will contain all that component's code) on request of that page in the browser.
Of course, all the above assumes you are using ES6 with import capabilities
there are at least 2 ways to achieve what you want, on of them (x-templates) already mentioned by Bill Criswell, but I think it worth to add an example
Define your component like this
Vue.component('my-checkbox', {
// id of x-template
template: '#my-template'
});
Add html file with your x-template (id should match the one you specified in the component)
<script type="text/x-template" id="my-template">...</script>
Another approach (and I like this one better) would be to use inline template
Define your template like this
Vue.component('my-template', {});
Add html file with your component and template inside it
<my-template inline-template>place for your html</my-template>
Just don't forget to add inline-template attribute, otherwise it won't work
You can use this approach with superagent:
var promise = superagent.get("something.html")
.end(function (error, response) {
if (error) {
console.error("load of something.html failed", error));
return;
}
var parser = new DOMParser()
var doc = parser.parseFromString(response.text, "text/html");
document.body.appendChild(doc.scripts[0]);
});
Just put your <script> tag based template inside of something.html on your server.
If you are using jQuery, .load should work.
Just make sure this completes before the DOM in question is compiled by Vue. Or use $mount to manually set things up.
Use browserify to bundle everything like this:
//Home.js
import Vue from 'vue';
var Home = Vue.extend({
template: require('./Home.vue')
});
export default Home;
//Home.vue
<h1>Hello</h1>
// And for your browserify bundle use a transform called stringify
... .transform(stringify(['.html', '.svg', '.vue', '.template', '.tmpl']));