VueJs 3 - Use bundled sfc combined with Client Side Rendering - javascript

Greetings
Hello fellow Vuers!
So I've got the following situation:
I use ASP.Net Core 3.1 as my server and I would like to use the Vue SFC setup including Typescript support and bind the resulting components into my .cshtml.
Example Usage
Example.vue
<template>
<label :for="name">{{ content }}</label>
<input :id="name" :placeholder="content"/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
someCode
}),
</script>
<style scoped>
/*some styling*/
</style>
Index.cshtml.cs
public class IndexModel: PageModel{
public string Name { get; set; }
public string Content { get; set; }
}
Index.cshtml
#page
#model Namespace.To.IndexModel
<example :name="#Model.Name" :content="#Model.Content" />
The Question
Is there a way to bundle the Single File Components and then use the Components as needed inside of a .(cs)html?
Preferably I'd like to have each component inside of it's own .js file to load them on demand, but it's not a must have.
Thanks in advance

It is possible. But to clarify, it seems like you want to use the components individually. That means that what you're calling a SFC/component, will need to be treated as its own Vue app.
Assuming that's the case, instead of making the components bundled separately you'll need to create a vue app for each one, and individually export them.
You will need to decide how/when to mount them. I've relied on a more manual way of mounting, which requires a line of js to link the DOM element with the widget and pass the attributes. Alternatively you can rely on the DOM tag only. Admittedly, the js way is a bit more verbose, but less prone to edge cases.
Here is the example for the js way
components/example/index.js:
import Example from './Example.vue';
export const mountExample = (el, props) =>
Vue.createApp(el, props);
Then will wrap the component in a function that allows you to pass the DOM element to use and the props.
You would need
<script src="vue.js"/><!-- if it's not bundled in there -->
<script src="example.js"/>
<div id="example" />
<script>
mountExample('#example', {name:"#Model.Name", content="#Model.Content});
</script>
The other way (without the js instantiation) would be to wrap it in a function (IIFE) that looks for <example> tags, parses the content and then mounts the app with the provided parameters. It's quite a bit more work than the other example, but shouldn't be terribly complex.
So for the Example example, I'd organize it something like this: example
/components/example/
|-- Example.vue
|-- index.js
and then use webpack chaining via vue.config.js to do multiple of these
module.exports = {
// tweak internal webpack configuration.
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
chainWebpack: config => {
// remove the standard entry point
config.entryPoints.delete('app')
// then add your own
config.entry('example')
.add('./src/components/example/index.js')
.end()
.entry('menu')
.add('./src/components/menu/index.js')
.end()
}
}
A caveat; I've used rollup to generate these and I haven't tested this, but webpack should work too
resources for the webpack config:
https://cli.vuejs.org/guide/webpack.html#simple-configuration
https://github.com/neutrinojs/webpack-chain

Related

How to convert component string as real component in vue

Hi i have a requirement for plugin store where i need to load component from core into plugins and inside plugin i need to convert that string into real component so that i can use it.
Note: better approach is most welcome in the view of plugin store inside core
my pseudo logic
get component from specified path through http
convert loaded string into real vue component and store it in a variable
render it in dom
let componentAsString =
`<template>
<div>
<h class="red">{{title}}</h>
<!--<A></A> -->
</div>
</template>
<script>
//import A from './components/A'
export default {
name: 'App',
data(){
return {
title:'Hello World'
}
},
/*components: {
A
}*/
}
</script>
<style lang="scss" scoped>
.red{color:red;}
</style>`;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>
You have chosen very difficult approach to solve the requirement. Main problem is the content of the string - it is essentially a Vue SFC (.vue file). In order to turn this SFC into a Vue component usable in the browser a lot must be done. You must use Webpack (or Rollup or any other bundler), use vue-loader inside to parse the SFC and use different Webpack loaders to process each section of SFC (Babel to transpile the <scipt> block, sass-loader and sass compiler to turn <style lang="scss"> into CSS)
There are tools for doing most of this (with limitations) in the browser like for example vue3-sfc-loader but the cost is huge - vue3-sfc-loader weights around 1.4MB of minified javascript (add Vue itself or potential CSS preprocessor on top of that) and I bet performance of such solution will not be great either
Much easier approach is to use this standard tooling at build time
Just create your components as you normally do - in .vue files
Use your components as Async Components and build a "dictionary" of available components. Those components will be build at build time into separate js files and loaded into a browser on demand (when used)
// ComponentStore.js
export default {
component1: () => import('./components/component1'),
component2: () => import('./components/component2'),
}
Note: process of creating this dictionary can be automated too (inspiration)
Use the component as dynamic component
// DynamicComponent.vue
<template>
<component :is="comp" />
</template>
<script>
import ComponentStore from "ComponentStore.js"
export default {
props: ['componentName'],
computed: {
comp() {
return ComponentStore[this.componentName]
}
}
}
</script>

Conditionally import assets in create-react-app

Is it possible to conditionally import assets when creating a React app using create-react-app? I'm aware of the require syntax - example:
import React from "react";
const path = process.env.REACT_APP_TYPE === "app_1" ? "app_1" : "app_2";
const imagePath = require(`./assets/${path}/main.png`);
export default function Test() {
return (
<img src={imagePath} alt="" />
);
}
This however bundles all my assets no matter what.
It will load the proper image, but it will still bundle all the files together in the final build.
When I look in the dev tools for the final build, I can see all the assets there even though I only wanted to load the assets for app_1.
Am I forced to touch the webpack config, if so, what should I change? or is there another way?
In the days when React didn't exist we didn't put assets into our JS files. We let the CSS to decide, what assets to load for what selectors. Then you could simply switch a corresponding class on or off for a corresponding element (or even the whole page) and viola it changes color, background, or even a form. Pure magic!
Ah. What times these were!
All above is true and I do not understand why would anyone do or recommend doing it differently. However if you still want to do it (for any reason) - you can! Latest create-react-app comes with out-of-the-box support for lazy loading of arbitrary components via dynamic importing and code splitting. All you need to do is use parenthesized version of the import() statement instead of the regular one. import() takes in a request string as usual and returns a Promise. That's it. Source code of the dynamicaly requested component won't be bundled in, but instead stored in separate chunks to be loaded on demand.
Before:
import OtherComponent from './OtherComponent';
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
After:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
Notice how function MyComponent part is identical.
For those wondering if it is tied to CRA or React, it's not. It's a generic concept that can be used in vanilla JavaScript.
You will need to use webpack (or other bundler.) The code is not being run when it's bundled, so the compiler has no way of knowing which branch of logic to follow (app_1 or app_2). Therefore you have to get into the bundler's logic in order to achieve your goal.
However, this isn't as scary as it seems since webpack has built in capability to do this (no 3rd parties required...)
I would look into using webpack.providePlugin
(https://webpack.js.org/plugins/provide-plugin)
or its sibling DefinePlugin
(https://webpack.js.org/plugins/define-plugin)
(I'm afraid these examples are off the top of my head, so it's very unlikely they'll work on first pass.)
Examples:
Both will require a provider module...
// in path/provider.js
module.exports = {
live: '/path/to/live/image',
dev: '/path/to/dev/image'
}
Provide Plugin Example
// in webpack
new webpack.ProvidePlugin({
imagePath: [
'path/provider', // the file defined above
process.env.ENVIRONMENT // either 'dev' or 'live'
]
}),
// in code
export default function Test() {
return (
<img src={imagePath} alt="" />
);
}
Define Plugin example:
// in webpack
new webpack.DefinePlugin({
'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT)
});
// in code
var providers = require('path/provider'); // same path provider as above
export default function Test() {
return (
<img src={providers[process.env.ENVIRONMENT]} alt="" />
);
}
In both cases the bundler is forced to collapse your variable to an actual literal value at compile time - before bundling has taken place. Since you have now collapsed the logical path down to a single option, it is now free to only bundle the relevant assets.
You can't do this with default CRA settings.
Because if your dynamic require or dynamic import path is not static, webpack won't be able to determine which assets to include in the final build folder, therefore, it will grab everything from your ./src folder, and put them all to your build folder.
There is a way to do it with default CRA settings
You can add to .env something like
REACT_APP_SKIN=1
Put your skin assets in public/css1, public/css2 etc. And include them in public/index.html using code like
<link href="/css%REACT_APP_SKIN%/theme.css" rel="stylesheet">

Importing javascript file for use within vue component

I am working on a project that requires using a js plugin. Now that we're using vue and we have a component to handle the plugin based logic, I need to import the js plugin file within the vue component in order to initialize the plugin.
Previously, this was handled within the markup as follows:
<script src="//api.myplugincom/widget/mykey.js
"></script>
This is what I tried, but I am getting a compile time error:
MyComponent.vue
import Vue from 'vue';
import * from '//api.myplugincom/widget/mykey.js';
export default {
data: {
My question is, what is the proper way to import this javascript file so I can use it within my vue component?
...
Include an external JavaScript file
Try including your (external) JavaScript into the mounted hook of your Vue component.
<script>
export default {
mounted() {
const plugin = document.createElement("script");
plugin.setAttribute(
"src",
"//api.myplugincom/widget/mykey.js"
);
plugin.async = true;
document.head.appendChild(plugin);
}
};
</script>
Reference: How to include a tag on a Vue component
Import a local JavaScript file
In the case that you would like to import a local JavaScript in your Vue component, you can import it this way:
MyComponent.vue
<script>
import * as mykey from '../assets/js/mykey.js'
export default {
data() {
return {
message: `Hello ${mykey.MY_CONST}!` // Hello Vue.js!
}
}
}
</script>
Suppose your project structure looks like:
src
- assets
- js
- mykey.js
- components
MyComponent.vue
And you can export variables or functions in mykey.js:
export let myVariable = {};
export const MY_CONST = 'Vue.js';
export function myFoo(a, b) {
return a + b;
}
Note: checked with Vue.js version 2.6.10
try to download this script
import * from '{path}/mykey.js'.
or import script
<script src="//api.myplugincom/widget/mykey.js"></script>
in <head>, use global variable in your component.
For scripts you bring in the browser way (i.e., with tags), they generally make some variable available globally.
For these, you don't have to import anything. They'll just be available.
If you are using something like Webstorm (or any of the related JetBrains IDEs), you can add /* global globalValueHere */ to let it know that "hey, this isn't defined in my file, but it exists." It isn't required, but it'll make the "undefined" squiggly lines go away.
For example:
/* global Vue */
is what I use when I am pulling Vue down from a CDN (instead of using it directly).
Beyond that, you just use it as you normally would.
I wanted to embed a script on my component and tried everything mentioned above, but the script contains document.write. Then I found a short article on Medium about using postscribe which was an easy fix and resolved the matter.
npm i postscribe --save
Then I was able to go from there. I disabled the useless escape from eslint and used #gist as the template's single root element id:
import postscribe from 'postscribe';
export default {
name: "MyTemplate",
mounted: function() {
postscribe(
"#gist",
/* eslint-disable-next-line */
`<script src='...'><\/script>`
);
},
The article is here for reference:
https://medium.com/#gaute.meek/how-to-add-a-script-tag-in-a-vue-component-34f57b2fe9bd
For anyone including an external JS file and having trouble accessing the jQuery prototype method(s) inside of the loaded script.
Sample projects I saw in vanilla JS, React and Angular were simply using:
$("#someId").somePlugin(options)
or
window.$("#someId").somePlugin(options)
But when I try either of those in my VueJS component I receive:
Error: _webpack_provided_window_dot$(...).somePluginis not a function
I examined the window object after the resources had loaded I was able to find the jQuery prototype method in the window.self read-only property that returns the window itself:
window.self.$("#someId").somePlugin(options)
Many examples show how to load the external JS file in VueJS but not actually using the jQuery prototype methods within the component.

How to import vuejs component dependencies in a MPA?

In a single page app, I can do this to include a component inside a component.
$ npm install sagalbot/vue-select
<template>
<div id="myApp">
<v-select :value.sync="selected" :options="options"></v-select>
</div>
</template>
<script>
import vSelect from "vue-select"
export default {
components: {vSelect},
data() {
return {
selected: null,
options: ['foo','bar','baz']
}
}
}
</script>
How can I do this in a MPA, where I have bunch of js files or sometimes inline javascript in different pages?
I am not using any build system.
I would highly recommend using vue-cli, but if for some reason that is not possible, then I believe,though I have never tried it, you would basically need to add all your components and code into a single long js file, or include them in the right order in your html documents.
Certain component libraries can work this way, like vuetify. You simply include the whole vuetify.js file after vue.js and then you can use all the components available.
I think it would be quite a lot of work to do anything of much size, but if it is something really small you could add components in the following manner, one after another.
var componentTemplate =
`
// Template code..
`;
Vue.component('my-cool-component', {
template: componentTemplate,
data: function() {
return {
//
}
}
});

Referencing in a simple way using AngularJS and TypeScript

I'm using VS2015 with Gulp and I'm trying to build AngularJS with TypeScript.
In my index.html, I have a script tag to my "app.js" (the output and bundle of the TS build).
However if I want to reference my controller, and any other services - how can I avoid having to put the relevant script tags in each and every HTML file - is there a way I can just reference the app.js file and be done with it? What's the recommended approach?
Cheers,
if you want to refrence only one file in html via script tag and other files just reference in other js files then you can use requirejs. It is a nice tool to load scripts. in production it is a good approach to concat and minify all your scripts to reduce number of requests.
I managed to resolve it using experimental typescript decorators, requirejs and a little of imagination.
So, in resume I wrote two decorators one called AppStartup and other called DependsOn. So at angular bootstrap I can get and register all dependencies.
I ended up with something like it:
TodoListController.ts
import {DependsOn, AppStartup, ngControllerBase} from "../infra/core";
import {taskManagerDirective} from "../directives/TaskManagerDirective";
#AppStartup({
Name: "TodoSample"
}).Controller({
DependsOn: [taskManagerDirective]
})
export class TodoListController extends ngControllerBase {
public ByControllerAs: string;
constructor() {
super(arguments);
let $httpPromise = this.$get<ng.IHttpService>("$http");
$httpPromise.then(function ($http) {
$http.get('http://www.google.com').success(function (body) {
console.info("google.com downloaded, source code: ", body);
});
});
this.ByControllerAs = "This was included by controllerAs 'this'";
}
}
rowListItemDirective.ts
import {DependsOn, ngDirectiveBase} from "../infra/core";
import {rowListItemDirective} from "./RowListItemDirective";
#DependsOn([rowListItemDirective])
export class taskManagerDirective extends ngDirectiveBase{
public template: string = `
Parent Directive
<table>
<tbody>
<tr row-list-item-directive></tr>
</tbody>
</table>
`;
}
You can see what I did below at my github repository:
https://github.com/klaygomes/angular-typescript-jasmine-seed

Categories

Resources