I'm creating babel plugin to manage import paths according to some conditions based on file existence. On initial build everything works fine, but when I add or remove file that would impact result of the conditions, compiler doesn't recompile file that is importing changed file, therefore conditions are still resolved as if referenced file existed, but compiler is not able to find it.
I believe this is due to cache. So how can I manage/clear cache? Of course I would like to keep as much cacheing as possible. So, maybe, is there way to programmatically remove from cache only files that were impacted by my plugin?
Basically logic of plugin is as follows:
const fs = require("fs");
const fs_path = require("path");
function shouldResoulveImport(node) {
/* some checks, returns bool */
}
module.exports = function(babel) {
return {
visitor: {
ImportDeclaration(path, state) {
if (!shouldResoulveImport(path.node)) {
return;
}
const existing_file = /* some checking for another file existence */;
if (existing_file) {
path.node.source.value = existing_file;
}
}
}
};
};
What i try to achieve is to use some default file for import, but when same file in other particular directory exists then change the import for the other file.
I use meteor 1.7 with babel 7 beta and react.
I've checked with babel support and it seems to be issue of meteor.
Thanks.
There was no way to achieving it in this manner.
So I decided to use try on import overriding file and exporting it further. And if the import fails, it means, that file doesn't exists so default file export its default content:
...
let toExport;
try {
import Overrides from "overriding_path";
toExport = Overrides;
} catch (e) {
toExport = OriginalExport;
}
export default toExport;
Later just build plugin that instead of modifying import modifies file.
Related
I'm assuming next.js isn't picking up the variables in my .env file? But I don't know how to fix that. Please and thank you for your time!
stripe.js
import Stripe from 'stripe';
const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY);
getStripe.js
import { loadStripe } from '#stripe/stripe-js';
let stripePromise;
const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
}
return stripePromise;
}
export default getStripe;
.env
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = *******
NEXT_PUBLIC_STRIPE_SECRET_KEY = *********
I've seen from similar posts that maybe I just need to change the .env file to ".env.local" but I tried that and it didn't seem to work. I just changed the name on the file, are there more steps to that?
I've also tried clearing the cache, restarting the whole project.
My Main goal is to create an Electron App (Windows) that locally stores data in an SQLite Database. And because of type safety I choose to use the Prisma framework instead of other SQLite Frameworks.
I took this Electron Sample Project and now try to include Prisma. Depending on what I try different problems do arrise.
1. PrismaClient is unable to be run in the Browser
I executed npx prisma generate and then try to execute this function via a button:
import { PrismaClient } from '#prisma/client';
onSqlTestAction(): void {
const prisma = new PrismaClient();
const newTestObject = prisma.testTable.create(
{
data: {
value: "TestValue"
}
}
);
}
When executing this in Electron I get this:
core.js:6456 ERROR Error: PrismaClient is unable to be run in the browser.
In case this error is unexpected for you, please report it in https://github.com/prisma/prisma/issues
at new PrismaClient (index-browser.js:93)
at HomeComponent.onSqlTestAction (home.component.ts:19)
at HomeComponent_Template_button_click_7_listener (template.html:7)
at executeListenerWithErrorHandling (core.js:15281)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:15319)
at HTMLButtonElement.<anonymous> (platform-browser.js:568)
at ZoneDelegate.invokeTask (zone.js:406)
at Object.onInvokeTask (core.js:28666)
at ZoneDelegate.invokeTask (zone.js:405)
at Zone.runTask (zone.js:178)
It somehow seems logical that Prisma cannot run in a browser. But I actually build a native app - with Electron that embeds a Browser. It seems to be a loophole.
2. BREAKING CHANGE: webpack < 5 used to include polyfills
So i found this Question: How to use Prisma with Electron
Seemed to be exactly what I looked for. But the error message is different (Debian binaries were not found).
The solution provided is to generate the prisma artifacts into the src folder instead of node_modules - and this leads to 19 polyfills errors. One for example:
./src/database/generated/index.js:20:11-26 - Error: Module not found: Error: Can't resolve 'path' in '[PATH_TO_MY_PROJECT]\src\database\generated'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
- install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "path": false }
And this repeats with 18 other modules. Since the error message to begin with was different I also doubt that this is the way to go.
I finally figured this out. What I needed to understand was, that all Electron apps consist of 2 parts: The Frontend Webapp (running in embedded Chromium) and a Node backend server. Those 2 parts are called IPC Main and IPC Renderer and they can communicate with each other. And since Prisma can only run on the main process which is the backend I had to send my SQL actions to the Electron backend and execute them there.
My minimal example
In the frontend (I use Angular)
// This refers to the node_modules folder of the Electron Backend, the folder where the main.ts file is located.
// I just use this import so that I can use the prisma generated classes for type safety.
import { TestTable } from '../../../app/node_modules/.prisma/client';
// Button action
onSqlTestAction(): void {
this.electronService.ipcRenderer.invoke("prisma-channel", 'Test input').then((value) => {
const testObject: TestTable = JSON.parse(value);
console.log(testObject);
});
The sample project I used already had this service to provide the IPC Renderer:
#Injectable({
providedIn: 'root'
})
export class ElectronService {
ipcRenderer: typeof ipcRenderer;
webFrame: typeof webFrame;
remote: typeof remote;
childProcess: typeof childProcess;
fs: typeof fs;
get isElectron(): boolean {
return !!(window && window.process && window.process.type);
}
constructor() {
// Conditional imports
if (this.isElectron) {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.webFrame = window.require('electron').webFrame;
this.childProcess = window.require('child_process');
this.fs = window.require('fs');
// If you want to use a NodeJS 3rd party deps in Renderer process (like #electron/remote),
// it must be declared in dependencies of both package.json (in root and app folders)
// If you want to use remote object in renderer process, please set enableRemoteModule to true in main.ts
this.remote = window.require('#electron/remote');
}
}
And then in the Electron backend I first added "#prisma/client": "^3.0.1" to the package.json (for the Electron backend not the frontend). Then I added to the main.ts this function to handle the requests from the renderer:
// main.ts
ipcMain.handle("prisma-channel", async (event, args) => {
const prisma = new PrismaClient();
await prisma.testTable.create(
{
data: {
value: args
}
}
);
const readValue = await prisma.testTable.findMany();
return JSON.stringify(readValue);
})
This way of simply adding the IPC Main handler in the main.ts file of course is a big code smell but usefull as minimal example. I think I will move on with the achitecture concept presented in this article.
I might be mixing up concepts, but I'd read that it's possible to get TestCafe to recognize variables of the form process.env.MY_COOL_VARIABLE. Also for my Vue.js frontend (built using Vue-CLI, which uses dotenv under the hood), I found I could make a file in .env.test for test values like so:
VUE_APP_MY_COOL_VARIABLE
which I would then access in my test code like so:
test('my fixture', async (t) => {
...
await t
.click(mySelector.find('.div').withText(process.env.VUE_APP_MY_COOL_VARIABLE));
...
}
However, I get the following error:
"text" argument is expected to be a string or a regular expression, but it was undefined.
Seems like my environment variables aren't getting picked up. I build my code like so: vue-cli-service build --mode test.
TestCafe doesn't provide support for .env files out of the box. You can create a test file that will require the dotenv module and load your configuration file:
// enable-dotenv.test.js
require('dotenv').config({ path: '.my.env' });
testcafe chrome enable-dotenv.test.js tests/
Here's how I solved my issue. When debugging, I did a console.log of process.env and noticed that the variable that vue recognizes wasn't visible during testcafe's run. From our package.json:
"test:ui:run": "VUE_APP_MY_COOL_VARIABLE=ui-test yarn build:test && testcafe -a ../ui-test-server.sh chrome",
Also this bit of javascript is run by both the test and mainline code, so I had to use a conditional.
import * as dotenv from 'dotenv';
if (process.env.npm_package_scripts_test_ui_run) { // are we running a testcafe script
dotenv.config({ path: '.env.test' });
}
Have you tried process.env[VUE_APP_MY_COOL_VARIABLE]? It's worth noting that everything in dotenv comes back as a string so you may need to do the casting yourself. For example:
function getEnvVariableValue(envVariable: string) {
// Cast to boolean
if (envVariableValue.toUpperCase() === "TRUE") {
return true;
} else if (envVariableValue.toUpperCase() === "FALSE") {
return false;
// Cast to number
} else if (!isNaN(Number(envVariableValue))) {
return Number(envVariableValue);
} else {
return envVariableValue;
}
}
You can also try creating a .env file in the root folder to see if it picks it that way. I use dotenv in my project directly by including it in the package.json as a dependency and it works this way.
How do I correctly import my collection definition?
I get this error message when as a result of trying to import
I externalized my collection definition FROM the main myMeteorApp.js file:
(My directory structure looked like this:)
/myMeteorApp
/myMeteorApp.js
...TO the tasks.js file:
(My directory structure currently looks like this:)
/myMeteorApp
--/imports/api/tasks.js
The contents of tasks.js look like this:
import { Mongo } from "meteor/mongo";
const Images = new FS.Collection("images", {
stores: [new FS.Store.FileSystem("images", {path: "~/uploads"})]
});
const buyList = new Mongo.Collection("BuyList");
const WhoAreWe = new Mongo.Collection("whoDb");
const merchantReviews = new Mongo.Collection("MerchantReviews");
const Messages = new Meteor.Collection("messages", {transform: function (doc) { doc.buyListObj = buyList.find({sessionIDz: {$in: [doc.buyList]}}); return doc; }});
export { Images };
export { buyList };
export { WhoAreWe };
export { merchantReviews };
export { Messages };
I have packages babel-preset-es2015 and ecmascript installed, but these haven't helped.
Looking forward to your help...
Everything is the chat session we had indicates that your original app used Meteor 1.2, which did not support ES2015 modules. In addition, you did not import them correctly.
Here is a short checklist for import-related issues:
Make sure that your project is actually using Meteor v1.3+ (run meteor --version). Earlier versions did not support the modules feature.
Make sure that you have the ecmascript package installed (run meteor list or cat .meteor/packages | grep ecmascript from your project's root directory. If not, meteor add ecmascript. This is the package used for compiling ES2015.
Make sure that you are using it correctly. There are 2 types of exports:
default - import foo from 'imports/bar' goes with export default bar.
named - import { foo } from 'imports/bar' goes with export const foo = ..., etc.
Folks, I'm trying to do something that I thought ought to be simple, but I must be doing something wrong. I'm trying to simply have a clear structure in my meteor application which uses Typescript.
Here are my requirements:
All interfaces are available in both client and server
Some class implementations are only available on the server
I don't want to rely on file load order for my application to work properly
I need my own module to not clash with global objects (such as the Position class for example)
I need to have one monolithic include file for server, one for both client and server and one for client (don't want to have 10s of includes on top of my files)
The setup that I have right now is this
server
server-book.ts
client
shared
collections.ts
definitions
server
include.d.ts (includes all .d.ts files in this folder)
server-book.d.ts (server specific implementation of book)
client
shared
include.d.ts (includes all .d.ts files here)
book.d.ts (book interface definition)
collections.d.ts
In each .d.ts file I have
module MyModule {
interface Bla {}
};
In each .ts file that defines a class I have:
module MyModule {
export class MyBla implements Bla {};
}
All .d.ts files generated for classes are generated by tsc -d.
No .ts files are being included via ///<reference> rather only .d.ts files.
Now, when I run this, I get an error that MyModule is undefined:
/// <reference path="shared/include.d.ts"/>
/// <reference path="server/include.d.ts"/>
Meteor.startup(() => {
var temp = new MyModule.ServerBook();
});
The error occurs right on MyModule.
What am I doing wrong? What should be the proper setup here?
Thanks!
I have dealt with this issue on my blog. I decided to use the evil eval command, since it gave me the easiest possibility of using modules till something more sophisticated appears.
File /lib/foo.ts is position in the subdirectory since it has to be loaded before Bar.
eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module
module Hugo {
export class Foo {
foo():string {
return 'foo'
}
}
}
File /bar.ts
/// <reference path="lib/foo.ts"/>
eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module
module Hugo {
export class Bar extends Foo {
bar () : string {
return 'bar';
}
}
}
File /test.ts
/// <reference path="lib/foo.ts"/>
/// <reference path="bar.ts"/>
var m = new Hugo.Bar();
console.log(m.bar());
console.log(m.foo());
As mentioned here, for classes, the solution is even simpler:
class ExportedClass {
variable : int;
}
this.ExportedClass = ExportedClass;
Definition files should use the declare keyword. You would normally get an error if you didn't use this keyword.
declare module MyModule {
export interface Bla {}
}
And
declare module MyModule {
export class MyBla implements Bla {
}
}
It is also worth checking that the ServerBook class has the export keyword (just like MyBla in your examples).
After lot of trial and errors, here are my findings so far :
Using typescript "module" keyword doesn't get well with Meteor. I think at the moment you cannot use it (or the workarounds are too complicated for me).
However, here is what you can do :
Let say that you have package A where you want to define a class ClassToExport which you want to make public.
class ClassToExport {
getFoo(){
return "foo";
}
}
Please note that you can't write this.ClassToExport = ClassToExport and
api.export('ClassToExport') or else ClassToExport won't be available in the global scope of package A, hence the need for a module/namespace for exporting your class, which we will see next.
Now, for the class to be available for the consumers of your package, you have to create a namespace, which will be the equivalent of the "module" typescript keyword for internal module.
So let's write :
declare var packageA; //so that the compiler doesn't complain about undeclared var
packageA = packageA || {}; //so that this namespace can be reused for the entire package
packageA.ClassToExport = ClassToExport; //the actual export
Now, don't forget to write
api.export('packageA') in the package.js of package A
If you have a package B where you want to use ClassToExport, you write in package B:
var cte = new packageA.ClassToExport();
without forgetting to api.use package A in package B's package.js
If you don't want to write the namespace each time you use the class, you can also write var ClassToExport = packageA.ClassToExport; at the top of your using file.
If you need a global class for you package only, without exporting it, then you can do instead just :
this.ClassToExport = ClassToExport
and again don't write api.export('ClassToExport'), or it won't be available in the package anymore.
This way, i think the features (export/ import) of internal typescript modules are there.
If you are not afraid of gulp build, I have prepared a typescript boilerplate project which allows you to comfortably use typescript from within your app, not depending on packages.
https://github.com/tomitrescak/meteor-boilerplate-typescript
Random idea, what about extend Meteor instead of Window.
Meteor.yournamespace = Meteor.yournamespace || {};
Meteor.yournamespace.myclass = new MyClass();
or
Meteor.yournamespace.MyClass = MyClass();
I think this is less invasive than go directly to the window object IMHO. my two cents.
now you can do Meteor.yournamespace.MyClass :P
--EDIT
Then you could create a meteor-extend.d.ts file and do something like:
/// <reference path="main.d.ts" />
declare module Meteor {
var yournamespace: any;
}
Now you can remove the <any> before Meteor and Typescript will not complaint.