I am working on a project on my Windows computer as well on my Macbook. I got a function that register components defined in certain directories globally like this:
require('./bootstrap');
import { createApp, h } from 'vue';
import { createInertiaApp } from '#inertiajs/inertia-vue3';
import { InertiaProgress } from '#inertiajs/progress';
/**
* Third-party imports.
*/
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
/**
* Register components from the ./components and ./components/core
* directories. This way these components don't have to be imported
* manually every time.
*
* The core components are the core of other components and contains
* alerts, notifications, input fields etc.
*
* Base components are bigger components made upon the core components
* and contain a combination of one or more of the core components. These
* are usually components that contain a lot of code and have to be re-used
* across the whole application.
*
* #param {Vue instance} app
*/
function registerGlobalComponents (app) {
const requireComponent = require.context(
'./components' || './components/core' || './components/base',
true,
/\.vue$/
);
for (const file of requireComponent.keys()) {
const componentConfig = requireComponent(file);
const name = file
.replace(/index.js/, '')
.replace(/^\.\//, '')
.replace(/\.\w+$/, '');
const componentName = upperFirst(camelCase(name));
app.component(componentName,
componentConfig.default);
}
}
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => require(`./Pages/${name}.vue`),
setup({ el, app, props, plugin }) {
const inertiaApp = createApp({ render: () => h(app, props) });
inertiaApp.use(plugin);
inertiaApp.mixin({ methods: { route } })
registerGlobalComponents(inertiaApp);
inertiaApp.mount(el);
},
});
InertiaProgress.init({ color: '#4B5563' });
When I run npm run watch or dev or prod it compiles the code and successfully shows me the website on my Windows machine. But when I do this on my Macbook it throws the following error:
Can't resolve './components' in '/Users/bas/Sites/trainfit/resources/js'
Is there a reason why this works on a Windows machine but not on a machine that runs on MacOS?
Some more information about my project:
Made with Vue and Inertiajs
Running this code from Visual Studio Code
As explained in the docs, you start by creating a jsconfig.json file in your root with these characters and symbols in it:
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
You can find examples and references here : - here
Related
Strapi Version: 4.3.0
Operating System: Ubuntu 20.04
Database: SQLite
Node Version: 16.16
NPM Version: 8.11.0
Yarn Version: 1.22.19
I have created Preview button for an article collection type. I'm using the Strapi blog template. I managed to make the Preview button appear in the Content Manager. I hard coded the link to be opened when you click the Preview button and it works. Now, I want the plugin to use a link with environment variables instead of a hard coded link. I don't know how I can access the environment variables in the source code for the plugin.
My objective:
I want to replace
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
with
href={${CLIENT_FRONTEND_URL}?secret=${CLIENT_SECRET}&slug=${initialData.slug}`}
in ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
where CLIENT_FRONTEND_URL and CLIENT_SECRET are environment variables declared like so in .env:
CLIENT_FRONTEND_URL=http://localhost:3000
CLIENT_PREVIEW_SECRET=abc
Here's a rundown of the code I used:
First, I created a strapi app using the blog template, then created a plugin.
// Create strapi app named backend with a blog template
$ yarn create strapi-app backend --quickstart --template #strapi/template-blog#1.0.0 blog && cd backend
// Create plugin
$ yarn strapi generate
Next, I created a PreviewLink file to provide a link for the Preview button
// ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
import React from 'react';
import { useCMEditViewDataManager } from '#strapi/helper-plugin';
import Eye from '#strapi/icons/Eye';
import { LinkButton } from '#strapi/design-system/LinkButton';
const PreviewLink = () => {
const {initialData} = useCMEditViewDataManager();
if (!initialData.slug) {
return null;
}
return (
<LinkButton
size="S"
startIcon={<Eye/>}
style={{width: '100%'}}
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
variant="secondary"
target="_blank"
rel="noopener noreferrer"
title="page preview"
>Preview
</LinkButton>
);
};
export default PreviewLink;
Then I edited this pregenerated file in the bootstrap(app) { ... } section only
// ./src/plugins/previewbtn/admin/src/index.js
import { prefixPluginTranslations } from '#strapi/helper-plugin';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import Initializer from './components/Initializer';
import PreviewLink from './components/PreviewLink';
import PluginIcon from './components/PluginIcon';
const name = pluginPkg.strapi.name;
export default {
register(app) {
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon: PluginIcon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: name,
},
Component: async () => {
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
return component;
},
permissions: [
// Uncomment to set the permissions of the plugin here
// {
// action: '', // the action name should be plugin::plugin-name.actionType
// subject: null,
// },
],
});
app.registerPlugin({
id: pluginId,
initializer: Initializer,
isReady: false,
name,
});
},
bootstrap(app) {
app.injectContentManagerComponent('editView', 'right-links', {
name: 'preview-link',
Component: PreviewLink
});
},
async registerTrads({ locales }) {
const importedTrads = await Promise.all(
locales.map(locale => {
return import(
/* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);
return Promise.resolve(importedTrads);
},
};
And lastly this created this file to enable the plugin Reference
// ./config/plugins.js
module.exports = {
// ...
'preview-btn': {
enabled: true,
resolve: './src/plugins/previewbtn' // path to plugin folder
},
// ...
}
I solved this by adding a custom webpack configuration to enable Strapi's admin frontend to access the environment variables as global variables.
I renamed ./src/admin/webpack.example.config.js to ./src/admin/webpack.config.js. Refer to the v4 code migration: Updating the webpack configuration from the Official Strapi v4 Documentation.
I then inserted the following code, with help from Official webpack docs: DefinePlugin | webpack :
// ./src/admin/webpack.config.js
'use strict';
/* eslint-disable no-unused-vars */
module.exports = (config, webpack) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
config.plugins.push(
new webpack.DefinePlugin({
CLIENT_FRONTEND_URL: JSON.stringify(process.env.CLIENT_FRONTEND_URL),
CLIENT_PREVIEW_SECRET: JSON.stringify(process.env.CLIENT_PREVIEW_SECRET),
})
)
return config;
};
I rebuilt my app afterwards and it worked.
You shouldn't have to change the webpack config just find .env file in the root directory
add
AWS_ACCESS_KEY_ID = your key here
then just import by
accessKeyId: env('AWS_ACCESS_KEY_ID')
So basically I have an Express API that I've been working on and recently I got the idea to implement a documentation for it. I chose Swagger and JSDoc to do this as it is quick and easy. However the documentation won't work. Here is how I've implemented it so far.
docs.ts:
import swaggerJSDoc, { Options } from "swagger-jsdoc";
const swaggerOptions: Options = {
definition: {
info: {
title: "Project Nonya Documentation.",
description: "Documentation for the Project Nonya API",
contact: {
name: "Joe Mama",
},
version: "6.9.0",
},
},
apis: ["./routes/*.routes.ts"],
};
export const swaggerDocs = swaggerJSDoc(swaggerOptions);
router.ts:
...
import swaggerUi from "swagger-ui-express";
import { swaggerDocs } from "./docs";
...
app.use("/api/documentation", swaggerUi.serve, swaggerUi.setup(swaggerDocs));
...
When I got to the docs endpoint, the documentation does appear, however its keeps saying No Operations defined in spec!. Here is one of the files in the routes folder.
testing.routes.ts:
import express, { Router } from "express";
import { testing} from "../controllers/testing/testing.controller";
const router: Router = express.Router();
/**
* #swagger
* /:
* get:
* description: Why you no work?
* responses:
* 200:
* description: Returns nothing cause this shit won't work.
*/
router.post("/testing/:id", testing);
export default router;
What am I doing wrong? I can't figure it out. The directories are like this: src/docs.ts, src/router.ts, and src/routes/testing.router.ts.
Side Question
Since I am using TypeScript, I obviously have to compile the code into JavaScript. However in the apis array in docs.ts, I specify files with a *.routes.ts. Would I have to change this to *routes.js.
Thank You!
Found this Github issue to be the solution to my problem.
https://github.com/Surnet/swagger-jsdoc/issues/150
I want to open the details settings of my application from the app itself.
I use the IntentLauncher from Expo itself: https://docs.expo.io/versions/latest/sdk/intent-launcher
The code I use that I assume should work is:
IntentLauncher.startActivityAsync(IntentLauncher.ACTION_APPLICATION_DETAILS_SETTINGS)
But this gives me this error:
[Unhandled promise rejection: Error: Encountered an exception while calling native method: Exception occurred while executing exported method startActivity on module ExpoIntentLauncher: null]
I'm not sure if I should give some kind of parameter with it so it links to my app?
Opening all other settings does work, ex:
IntentLauncher.startActivityAsync(IntentLauncher.ACTION_APPLICATION_SETTINGS)
This does open a list off all apps, I just need to get the detailed screen of the app itself, not the list.
I found this solution by bodolsog working.
Complete solution
import * as IntentLauncher from "expo-intent-launcher";
import Constants from "expo-constants";
const pkg = Constants.manifest.releaseChannel
? Constants.manifest.android.package // When published, considered as using standalone build
: "host.exp.exponent"; // In expo client mode
IntentLauncherAndroid.startActivityAsync(
IntentLauncherAndroid.ACTION_APPLICATION_DETAILS_SETTINGS,
{ data: 'package:' + pkg },
)
Hope this helps someone
import { startActivityAsync, ActivityAction } from 'expo-intent-launcher';
import * as Linking from 'expo-linking';
import Constants from 'expo-constants';
const pkg = Constants.manifest.releaseChannel
? Constants.manifest.android.package // When published, considered as using standalone build
: 'host.exp.exponent'; // In expo client mode
openSettings = async () => {
try {
if (Platform.OS === 'android') {
// console.log(Constants);
startActivityAsync(ActivityAction.APPLICATION_DETAILS_SETTINGS, {
data: 'package:' + pkg,
});
}
if (Platform.OS === 'ios') {
Linking.openSettings();
}
} catch (error) {
console.log(error);
}
};
The purpose of this task is to make it impossible to download the Vue-component package (*.js file) knowing the address of the component, but not having an access token.
I'm developing an access control system and a user interface in which the set of available components depends on the user's access level.
The system uses the JSON API and JWT authorization. For this, Axios is used on the client side. To build the application, we use Webpack 4, to load the components, we use the vue-loader.
After the user is authorized, the application requests an array of available routes and metadata from the server, then a dynamically constructed menu and routes are added to the VueRouter object.
Below I gave a simplified code.
import axios from 'axios'
import router from 'router'
let API = axios.create({
baseURL: '/api/v1/',
headers: {
Authorization: 'Bearer mySecretToken12345'
}
})
let buildRoutesRecursive = jsonRoutes => {
let routes = []
jsonRoutes.forEach(r => {
let path = r.path.slice(1)
let route = {
path: r.path,
component: () => import(/* webpackChunkName: "restricted/[request]" */ 'views/restricted/' + path)
//example path: 'dashboard/users.vue', 'dashboard/reports.vue', etc...
}
if (r.children)
route.children = buildRoutesRecursive(r.children)
routes.push(route)
})
return routes
}
API.get('user/routes').then(
response => {
/*
response.data =
[{
"path": "/dashboard",
"icon": "fas fa-sliders-h",
"children": [{
"path": "/dashboard/users",
"icon": "fa fa-users",
}, {
"path": "/dashboard/reports",
"icon": "fa fa-indent"
}
]
}
]
*/
let vueRoutes = buildRoutesRecursive(response.data)
router.addRoutes(vueRoutes)
},
error => console.log(error)
)
The problem I'm having is because Webpack loads the components, by adding the 'script' element, and not through the AJAX request. Therefore, I do not know how to add an authorization header to this download. As a result, any user who does not have a token can download the code of the private component by simply inserting his address into the navigation bar of the browser.
Ideally, I would like to know how to import a vue component using Axios.
Or, how to add an authorization header to an HTTP request.
I needed something similar and came up with the following solution. First, we introduce a webpack plugin that gives us access to the script element before it's added to the DOM. Then we can munge the element to use fetch() to get the script source, and you can craft the fetch as needed (e.g. add request headers).
In webpack.config.js:
/*
* This plugin will call dynamicImportScriptHook() just before
* the script element is added to the DOM. The script object is
* passed to dynamicImportScriptHook(), and it should return
* the script object or a replacement.
*/
class DynamicImportScriptHookPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"DynamicImportScriptHookPlugin", (compilation) =>
compilation.mainTemplate.hooks.jsonpScript.tap(
"DynamicImportScriptHookPlugin", (source) => [
source,
"if (typeof dynamicImportScriptHook === 'function') {",
" script = dynamicImportScriptHook(script);",
"}"
].join("\n")
)
);
}
}
/* now add the plugin to the existing config: */
module.exports = {
...
plugins: [
new DynamicImportScriptHookPlugin()
]
}
Now, somewhere convenient in your application js:
/*
* With the above plugin, this function will get called just
* before the script element is added to the DOM. It is passed
* the script element object and should return either the same
* script element object or a replacement (which is what we do
* here).
*/
window.dynamicImportScriptHook = (script) => {
const {onerror, onload} = script;
var emptyScript = document.createElement('script');
/*
* Here is the fetch(). You can control the fetch as needed,
* add request headers, etc. We wrap webpack's original
* onerror and onload handlers so that we can clean up the
* object URL.
*
* Note that you'll probably want to handle errors from fetch()
* in some way (invoke webpack's onerror or some such).
*/
fetch(script.src)
.then(response => response.blob())
.then(blob => {
script.src = URL.createObjectURL(blob);
script.onerror = (event) => {
URL.revokeObjectURL(script.src);
onerror(event);
};
script.onload = (event) => {
URL.revokeObjectURL(script.src);
onload(event);
};
emptyScript.remove();
document.head.appendChild(script);
});
/* Here we return an empty script element back to webpack.
* webpack will add this to document.head immediately. We
* can't let webpack add the real script object because the
* fetch isn't done yet. We add it ourselves above after
* the fetch is done.
*/
return emptyScript;
};
Although sspiff's answer looks quite promising, it did not work directly for me.
After some investigation this was mainly due to me using Vue CLI 3 and thus a newer version of webpack. (which is kinda weird as sspiff mentioned using webpack 4.16.1).
Anyway to solve it I used the following source: medium.com,
Which gave me the knowledge to edit the given code.
This new code is situated in vue.config.js file:
/*
* This plugin will call dynamicImportScriptHook() just before
* the script element is added to the DOM. The script object is
* passed to dynamicImportScriptHook(), and it should return
* the script object or a replacement.
*/
class DynamicImportScriptHookPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"DynamicImportScriptHookPlugin", (compilation) =>
compilation.mainTemplate.hooks.render.tap(
{
name: "DynamicImportScriptHookPlugin",
stage: Infinity
},
rawSource => {
const sourceString = rawSource.source()
if (!sourceString.includes('jsonpScriptSrc')) {
return sourceString;
} else {
const sourceArray = sourceString.split('script.src = jsonpScriptSrc(chunkId);')
const newArray = [
sourceArray[0],
'script.src = jsonpScriptSrc(chunkId);',
"\n\nif (typeof dynamicImportScriptHook === 'function') {\n",
" script = dynamicImportScriptHook(script);\n",
"}\n",
sourceArray[1]
]
return newArray.join("")
}
}
)
);
}
}
module.exports = {
chainWebpack: (config) => {
config.plugins.delete('prefetch')
},
configureWebpack: {
plugins: [
new DynamicImportScriptHookPlugin()
]
}
}
The second piece of code provided by sspiff has stayed the same and can be placed in the App.vue file or the index.html between script tags.
Also to further improve this answer I will now explain how to split the chunks in Vue CLI 3 for this specific purpose.
as you can see I also added the chainWebpack field to the config. This makes sure that webpack does not add prefetch tags in the index.html. (e.g. it will now only load lazy chunks when they are needed)
To further improve your splitting I suggest changing all your imports to something like:
component: () => import(/* webpackChunkName: "public/componentName" */ /* webpackPrefetch: true */'#/components/yourpubliccomponent')
component: () => import(/* webpackChunkName: "private/componentName" */ /* webpackPrefetch: false */'#/components/yourprivatecomponent')
This will make sure that all your private chunks end up in a private folder and that they will not get prefetched.
The public chunks will end up in a public folder and will get prefetched.
For more information use the following source how-to-make-lazy-loading-actually-work-in-vue-cli-3
Hope this helps anyone with this problem!
To perform a simple component download using an access token, you can do the following...
1) Use asynchronous component loading with file extraction. Use webpackChunkName option to separate file or directory/file, like:
components: {
ProtectedComp: () => import(/* webpackChunkName: "someFolder/someName" */ './components/protected/componentA.vue')
}
2) Configure server redirection for protected files or direcory. Apache htaccess config for example:
RewriteRule ^js/protected/(.+)$ /js-provider.php?r=$1 [L]
3) write a server-side script that checks the token in the header or cookies and gives either the contents of .js or 403 error.
I'm in the process of upgrading an AngularJS v1.5 project to Angular 4.x. During development of the original AngularJS application, we would use the ngMocks package to simulate actual web service API responses, and display the data accordingly on the page. This was incredibly helpful during development as I didn't have to hard-code values for removal later on. Best of all, we configured Webpack to only include the mock data during development, and ignore those mock data files when building our application for production use. The mock data was configured like this:
/* app-login.mock.js */
import angular from 'angular';
import 'angular-mocks';
angular.module('app').run(function ($httpBackend) {
$httpBackend
.whenPOST('./api/auth')
.respond(function(method, url, data) {
var credentials = angular.fromJson(data);
if (credentials.username == 'gooduser') {
return [200, {'token': createToken(credentials.username)}];
} else {
return [401, {'errorMsg': 'Mock login only allows username "gooduser"'}];
}
});
});
function createToken(username) {
// Create a token, which is valid enough for testing purposes.
// This token is based on the actual token generated by the web service.
let currentTime = new Date();
let futureTime = new Date(currentTime.getTime() + ((currentTime.getHours() + 8) * 60 * 60 * 1000));
let header = {
alg: 'HS512'
};
let payload = {
exp: futureTime.getTime() / 1000,
sub: username,
roles: 'SOME_APPLICATION_ROLES',
iat: currentTime.getTime() / 1000
};
return `${btoa(angular.toJson(header))}.${btoa(angular.toJson(payload))}`;
}
Webpack was then configured to include all "mock" files into the built bundle, which could then be displayed as if it were a real HTTP response.
/* webpack.config.js */
const isProd = process.env.NODE_ENV === 'production';
const entry = {
app: (() => {
let app = [
'babel-polyfill',
path.join(PATHS.app, 'pollyfills.ts'),
path.join(PATHS.app, 'main.ts')
];
if (isProd) {
app.push(path.join(PATHS.app, 'app.prod.js'));
} else {
app.push(path.join(PATHS.app, 'app.mock.js'));
}
return app;
})()
};
module.exports = {
entry,
// ...other exports
};
And then the app.mock.js file:
/* app.mock.js */
var mockContext = require.context(".", true, /\.mock$/);
mockContext.keys().forEach(mockContext);
I've scoured the internet looking for a solution that works just as well as our old one, though I haven't come up with any good answers. Best I've found are tutorials on how to set up Unit Tests that return mock data, and while that's useful for testing functionality it doesn't help me test the application during the development process.
I also have seen some documentation on setting up Interceptors using the new HttpClient class found within Angular 4, but I'm not sure how to add it to our Webpack configuration under the condition of only being allowed during development. Does anyone have any advice on what to do?
I use the angular-in-memory-web-api. You can find it here: https://github.com/angular/in-memory-web-api
UPDATE: The repo was moved here, within the angular/angular repo: https://github.com/angular/angular/tree/e0dfa42d6e656124f3c3d78e178b1bf091b38e79/packages/misc/angular-in-memory-web-api
It intercepts all of your http calls and works with sample data you provide.
To change from dev to production, you need to remove the imports. Or you could possibly write two different modules, one with the dev imports and one with the production imports and include one or the other with webpack similar to what you do now. (But I have not tried this.)
You set up your data like this:
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { IProduct } from './product';
export class ProductData implements InMemoryDbService {
createDb() {
let products: IProduct[] = [
{
'id': 1,
'productName': 'Leaf Rake',
'productCode': 'GDN-0011',
'releaseDate': 'March 19, 2016',
'description': 'Leaf rake with 48-inch wooden handle.',
'price': 19.95,
'starRating': 3.2,
'imageUrl': 'http://openclipart.org/image/300px/svg_to_png/26215/Anonymous_Leaf_Rake.png',
'tags': ['rake', 'leaf', 'yard', 'home']
},
// ...
];
return { products };
}
}
And you build your data access service using the normal Http or HttpClient.
I have a full example with all CRUD operations here: https://github.com/DeborahK/Angular2-ReactiveForms