I am trying to break out a Vue/Vuex project into an npm package hosted privately. I think I am getting there however I am unsure of my current layout, so far I have:
src/
├── BrokerProposal
│ ├── FormInputs
│ ├── index.js
│ └── modals
└── store
├── index.js
└── modules
└── proposal
├── actions
├── getters
├── helpers
├── mutations
└── states
└── validations
My aim is to make the BrokerProposal directory importable which is done via the first index.js file:
const BrokerProposal = require('./BrokerCalculator.vue');
function install(Vue) {
if (install.installed) return;
install.installed = true;
Vue.component("v-broker-proposal", BrokerProposal);
}
const plugin = {
install
};
let GlobalVue = null;
if (typeof window !== "undefined") {
GlobalVue = window.Vue;
} else if (typeof global !== "undefined") {
GlobalVue = global.vue;
}
if (GlobalVue) {
GlobalVue.use(plugin);
}
BrokerProposal.install = install;
export default BrokerProposal;
This project also uses vuex so I have broken out the mutators etc of the state into this package to go along with the BrokerProposal, the end user can then bind this store once importing, here is the index.js file:
import Vue from 'vue'
import Vuex from 'vuex'
// Vuex Modules
import tabs from './modules/tabs'
import proposal from './modules/proposal'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
tabs,
proposal,
},
})
export default store
I feel like I should be including another index.js file in on the same level as /src as there is a section 'main' in the package.json file that must point to something?
Side effects like Vue.use(Vuex) and GlobalVue.use(plugin) should be avoided because they may interfere with a project that consumes this package. It's the responsibility of a project to set up plugins with appropriate Vue instance.
All public exports can be named exports in entry point, e.g. src/index.js:
export { default as BrokerProposal } from './BrokerProposal';
export { default as store } from './store';
Also it's a good practice to export components, in case they need to be imported locally instead of relying on global registration with Vue.component.
Related
I have a Vue project that is able to load other Vue components bundled as .mjs files. I want to develop all those pluggable components inside a repository but instead of distributing all pluggable components in a single build I want to tell Vite which component to build. If that works I don't need to think about dealing with a Monorepo, Multirepo or something else.
After creating the library project I thought about organizing each plugin into a separate folder in the src directory
.
└── src
├── textWithBlueBackground
| ├── index.ts ( importing "TextWithBlueBackground" and exporting as "Renderer" )
| └── TextWithBlueBackground.vue
└── textWithRedBackground
├── index.ts ( importing "TextWithRedBackground" and exporting as "Renderer" )
└── TextWithRedBackground.vue
The problem is that I need to switch to library mode but I don't know what to pass in there
build: {
lib: {
entry: resolve(__dirname, "./src/index.ts"), // this is wrong
name: "Renderer",
fileName: "renderer",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
},
After fixing that... Is it possible to tell Vite ( via CLI flag ) to only build a specific sub directory? E.g.
vite build ./src/{folderName}
If that's not possible I could create an index.ts in src
import { Renderer as TextWithBlueBackground } from "./textWithBlueBackground";
import { Renderer as TextWithRedBackground } from "./textWithRedBackground";
export { TextWithBlueBackground, TextWithRedBackground }
but how can I tell Vite to only build a specific component then?
The generated .mjs file should only contain the desired component, preferably as "Renderer" but I think the component name should be fine too.
recently I've been working on a chrome extension that uses vue as the frontend. The vue boilerplate that enables the extension to run on the browser uses webpack and is downloaded with:
vue init kocal/vue-web-extension name
and gives this project structure :
.
├── dist
│ └── <the built extension>
├── node_modules
│ └── <one or two files and folders>
├── package.json
├── package-lock.json
├── scripts
│ ├── build-zip.js
│ └── remove-evals.js
├── src
│ ├── background.js
│ ├── icons
│ │ ├── icon_128.png
│ │ ├── icon_48.png
│ │ └── icon.xcf
│ ├── manifest.json
│ └── popup
│ ├── App.vue
│ ├── popup.html
│ └── popup.js
└── webpack.config.js
The problem with this setup is that now I'm trying to implement OCR using tesseract.js and because chrome extensions don't let you use CDNs or outside libraries I need to download tesseract.js files locally. I looked through this link about downloading locally and also referenced tesseract.js' example on using tesseract.js with chrome extension (https://github.com/jeromewu/tesseract.js-chrome-extension), however when I'm loading the library I keep encountering the problem
tesseract.min.js:688 Uncaught Error: ReferenceError: window is not defined
at eval (tesseract.min.js:688)
at Worker.e.onmessage (tesseract.min.js:1579)
The current tesseract code I have right now in a vue file is (App.vue) and the problem seems to happen on await worker.load():
const { createWorker } = Tesseract;
const worker = createWorker({
workerPath: chrome.runtime.getURL("js/worker.min.js"),
langPath: chrome.runtime.getURL("traineddata"),
corePath: chrome.runtime.getURL("js/tesseract-core.wasm.js")
});
async function extract() {
console.log("test1");
await worker.load();
console.log("test2");
await worker.loadLanguage("eng");
await worker.initialize("eng");
const {
data: { text }
} = await worker.recognize("https://tesseract.projectnaptha.com/img/eng_bw.png");
console.log(text);
await worker.terminate();
}
extract();
Html page includes (tab.html):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="tab.css" />
<script src="../js/tesseract.min.js"></script>
</head>
<body>
<div id="app"></div>
<script src="tab.js"></script>
</body>
</html>
and js file (tab.js):
import Vue from "vue";
import App from "./App";
/* eslint-disable no-new */
new Vue({
el: "#app",
render: h => h(App)
});
My current file structure looks like this:
File structure
I've been stuck on this problem for quite a while now so any help would be greatly appreciated!
Although I can't help you with your question per se (and it's been over six months without anyone else answering), I thought I'd let you know how I solved my similar problem.
I too wanted an OCR function in a chrome extension and started digging into tesseract to begin with. When I couldn't solve it I moved on and instead used OCRAD.js and GOCR.js for my project. Although perhaps not quite as powerful as tesseract, I'm fully satisfied with my result.
Both OCRAD and GOCR are simple to include in your manifest.json and then you call the functions in your script by calling them as functions: OCRAD(image) or GOCR(image).
OCRAD has a nice demo page where you can test the functionality for your desired images before using it: https://antimatter15.com/ocrad.js/demo.html
I am creating documentation for a W3C web components library (Vanilla JavaScript) using VuePress. However, my "custom" web components are generating an error due to VuePress trying to "recognize" them as Vue components the very first time the page loads.
Once the page is loaded my web components work as expected, but the error is there anyway.
This is the error I am getting:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Unknown custom element: <nova-timeline> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
---> <TimeLineWrapper> at docs/.vuepress/components/TimeLineWrapper.vue
I have created the following structure related to Vuepress
.
├── docs
│ ├── .vuepress
│ │ ├── components
│ │ │ ├── TimeLineWrapper.vue
│ │ ├── config.js
│ │ └── theme
│ ├── README.md
│ ├── components
│ │ ├── README.md
│ │ └── Timeline.md
And this is part of my code:
// docs/.vuepress/components/TimeLineWrapper.vue
<template>
<nova-timeline ref="timeline"></nova-timeline>
</template>
<script>
import timeLineJson from "./data/TimeLineData";
export default {
data() {
return {
timeLineJson: timeLineJson
};
},
mounted() {
this.$refs.timeline.data = this.timeLineJson.data;
this.$refs.timeline.configuration = this.timeLineJson.configuration;
}
};
</script>
// This is my W3C web component:
<nova-timeline ref="timeline"></nova-timeline>
What I like to know is how to "ignore custom components", I mean where or how to do this kind of configuration using the VuePress way.
Vue.config.ignoredElements = [
// Use a `RegExp` to ignore all elements that start with "nova-"
// 2.5+ only
/^nova-/
]
https://v2.vuejs.org/v2/api/#ignoredElements
Thanks in advance.
I finally manage to find how to add my ignored elements,
1) Create a file named enhanceApp.js in docs/.vuepress/theme
2) Place this content inside of it:
// https://vuepress.vuejs.org/guide/custom-themes.html#app-level-enhancements
export default ({ Vue, options, router, siteData }) => {
Vue.config.ignoredElements = [
// Use a `RegExp` to ignore all elements that start with "nova-"
// 2.5+ only
/^nova-/
];
}
Now, the error will disappear since Vue will ignore our custom web components.
import Vue from 'vue';
import Router from 'vue-router';
//
// Application routes
//
const appRoutes = []
//
// let's add 404 page
// in the case we have landed on non-processed URL
//
import PageNotFound from '#/lib/components/page-not-found';
appRoutes.push({
path: '*',
name: 'PageNotFound',
component: PageNotFound,
meta: {
layout: 'DefaultLayout',
},
});
Vue.use(Router);
export default new Router({
mode: 'history',
saveScrollPosition: true,
routes: appRoutes,
});
Such code does not display anything in the browser when I hit any URL (as router is configured for path '*'), however if I replace to async load
appRoutes.push({
path: '*',
name: 'PageNotFound',
component: () => import('#/lib/components/page-not-found'),
meta: {
layout: 'DefaultLayout',
},
});
then everything starts to work.
Chrome console displays NO errors
2865:├─┬ eslint-plugin-vue#4.5.0
2866:│ └─┬ vue-eslint-parser#2.0.3
3912:├─┬ jest-serializer-vue#1.0.0
5629:│ └── vue-resize#0.4.4
5631:├── vue#2.5.16
5632:├─┬ vue-alertify#1.0.5
5640:│ └── vue#2.5.16 deduped
5641:├── vue-autosuggest#1.4.1
5642:├── vue-axios#2.1.1
5643:├── vue-class-component#6.2.0
5644:├── vue-error-boundary#1.0.1
5645:├── vue-extend-layout#1.1.2
5646:├── vue-head#2.0.12
5647:├─┬ vue-highlightable-input#1.0.5
5652:├── vue-i18n#7.8.1
5653:├─┬ vue-jest#2.6.0
5675:│ └── vue-template-es2015-compiler#1.6.0
5676:├─┬ vue-loader#14.2.3
5693:│ ├── vue-hot-reload-api#2.3.0
5694:│ ├── vue-style-loader#4.1.0 deduped
5695:│ └── vue-template-es2015-compiler#1.6.0 deduped
5696:├─┬ vue-meta#1.5.0
5700:├── vue-momentjs#0.1.2
5701:├─┬ vue-multi-select#3.5.1
5702:│ └── vue#2.5.16 deduped
5703:├── vue-progress-path#0.0.2
5704:├─┬ vue-property-decorator#6.1.0
5706:│ └── vue-class-component#6.2.0 deduped
5707:├─┬ vue-resize-directive#1.0.1
5710:├─┬ vue-responsive-components#0.2.3
5713:├── vue-router#3.0.1
5714:├─┬ vue-routisan#2.1.1
5715:│ └── vue-router-multiguard#1.0.3
5716:├── vue-select#2.4.0
5717:├── vue-spinner#1.0.3
5718:├─┬ vue-style-loader#4.1.0
5721:├─┬ vue-styled-components#1.2.3
5746:├── vue-svg-sprite#1.2.3
5747:├── vue-table-component#1.9.1
5748:├─┬ vue-template-compiler#2.5.16
5751:├── vue-toasted#1.1.24
5752:├── vue-truncate-filter#1.1.7
5753:├── vue-upload-component#2.8.9
5754:├── vue2-filters#0.3.0
5755:├─┬ vue2-sentry#1.2.1
5757:│ └── vue#2.5.16 deduped
5758:├── vue2-transitions#0.2.3
5759:├─┬ vuejs-uploader#0.6.5
5760:│ └── vue#2.5.16 deduped
5761:├── vuex#3.0.1
5762:├── vuex-cache#1.1.1
5763:├── vuex-class#0.3.1
5764:├── vuex-loading#0.3.0
5765:├─┬ vuex-search#2.2.1
Any ideas?
So I had to run
vue init webpack
and then started moving the code from the project one folder by another, at the very end everything worked just fine without any code modifications
As I was copying the folders one by one I was checking if code compiles against simple HelloWorld component that vue init has generated, then started to import configs, stores etc and then finally everything worked with non-async import of the component in the router config.
Before I have started a new project by cloning the folder (cp -R) and maybe that was an issue.
I'm building a Vue web application with Vue Router and Vuex, using vue-cli for scaffolding. The majority of the project structure is very simple, with one main store and simple routing:
project/
├── src/
│ ├── components/
│ │ ├── Hosting/
│ │ └── Website/
│ ├── router/
│ ├── store/
│ ├── App.vue
│ └── main.js
└── index.html
The Hosting component, routed at /hosting, needs to be decoupled from the rest of the website. That URL will host a component that will be loaded on other websites using an <iframe>. What's the best way to accomplish this logic decoupling? I'd thought about including a store directory in the Hosting folder, but wasn't sure how to inject that into the Hosting root component. I looked into using modules, but this doesn't really accomplish what I want since all of the modules are accessible from every component.
Note that I don't actually need the /hosting endpoint to use the same routing as the rest of the website, since it will only ever be accessed as the src of an <iframe>. So if I need to do something with how webpack compiles the project (like creating a hosting.html target in addition to index.html), I could do that.
You can consider the answer that Joachim gave you, I mean split to 2 apps, and I am doing it on my application (providing the configuration on webpack)
webpack.config.js
...
entry: {
'./app-1.js': './src/app1/main.js',
'./app-2.js': './src/app2/main.js'
},
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/build/',
filename: '[name]'
},
...
If you need to access the same store from both apps, I think you should re-consider the modules solution in vuex. I am splitting my store into modules and load the wanted modules in each application (in App.vue in the beforeCreate lifecycle hook). Looks something like this:
main1.js
import Vue from 'vue'
import App from './App.vue'
import { store } from '../store/store';
new Vue({
el: '#app2',
store: store,
render: h => h(App)
});
main2.js
import Vue from 'vue'
import App from './App.vue'
import { store } from '../store/store';
new Vue({
el: '#app1',
store: store,
render: h => h(App)
});
(please note that both apps load same store)and then:
App1.vue
export default {
...
beforeCreate() {
// this is where we add dynamically specific modules used on this app.
const store = this.$store;
const modulesToBoDynamicallyRegistered = {
[Types.module1.TYPE_NAME]: module1,
[Types.module2.TYPE_NAME]: module2,
[Types.module3.TYPE_NAME]: module3,
[Types.module4.TYPE_NAME]: module4,
};
Object.keys(modulesToBoDynamicallyRegistered).forEach((moduleName) => {
utils.registerModuleIfNotExists(store, moduleName, modulesToBoDynamicallyRegistered[moduleName]);
});
},
...
}
App2.vue
export default {
...
beforeCreate() {
// this is where we add dynamically specific modules used on this app.
const store = this.$store;
const modulesToBoDynamicallyRegistered = {
[Types.module7.TYPE_NAME]: module7,
[Types.module9.TYPE_NAME]: module9,
};
Object.keys(modulesToBoDynamicallyRegistered).forEach((moduleName) => {
utils.registerModuleIfNotExists(store, moduleName, modulesToBoDynamicallyRegistered[moduleName]);
});
},
...
}
the store itself holds the common modules:
store.js
import Vue from 'vue';
import Vuex from 'vuex';
import module5 from './modules/module5/index';
import module6 from './modules/module6/index';
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
module5,
module6,
}
});
if you need the registerModuleIfNotExists function:
registerModuleIfNotExists: (store, moduleName, module) => {
if (!(store && store.state && store.state[moduleName])) {
store.registerModule(moduleName, module);
} else {
// re-use the already existing module
throw `reusing module: ${moduleName}`;
}
},
Consider the Hosting component as a completely separate app would give you the freedom to host it on other sites as well. So yes, create a hosting.html page to show in the iframe and in other sites.
That would make your own usage of the component align completely with how it is hosted on other sites and make finding bugs easier as well.
You did not mention if the hosting component requires any data or parameters from the containing structure, but if so, you should provide this in the query string of hosting.html.