SoftwareBrothers / adminjs

AdminJS is an admin panel for apps written in node.js
https://adminjs.co
MIT License
8.21k stars 662 forks source link

Fix AdminBro.bundle for typescript and any other precompilers (babel) #360

Closed wojtek-krysiak closed 4 years ago

wojtek-krysiak commented 4 years ago

Right now when you build an application with typescript there is an issue with AdminBro.bundle method. Namely, bundle(path) looks for a relative (to current) component file. Which is ok when you work with javascript, but when you work with ts most probably at some point you will want to build your files.... and this brakes everything.

Let say you have a file src/admin.resource.js where you use the bundle method:

const adminResource:ResourceOptions = {
  properties: {
    map: {
      components: { show: AdminBro.bundle('./show-map') }
    }
  }
}

and when you run tsc with outDir set to /dist all custom components will be transpiled by tsc so now AdminBro.bundle('./show-map') actually loads already transpiled dist/admin.resource.js and it cannot handle that. Now in order to fix this you have to specify path relative to root of your app: AdminBro.bundle('../src/show-map') so it always requires an original file.

With nested files it could look like this: AdminBro.bundle('../../../src/admin/resources/show-map') which is nasty.

Acceptance criteria

pincheira commented 4 years ago

Thanks so much @wojtek-krysiak for posting the solution. I wasted like 4 hours trying to fix this, until I found this open issue via google.

It was all compiling for me locally, but when I ran this through heroku local, which needs the final build by tsc, it would not want to load the components.

I just changed AdminBro.bundle('./components/avatar'), to AdminBro.bundle('../../src/admin/components/avatar.jsx'), and voila! Now it works perfect.

BeaRRRRR commented 4 years ago

Still doesn't work for me, its a shame AdminBro doesn't support TS out of the box.

My webpack.config.js is

const path = require('path');
const nodeExternals = require('webpack-node-externals');
const { NODE_ENV = 'development' } = process.env;

module.exports = {
    entry: './src/index.ts',

    mode: 'development',
    target: 'node',

    output: {
        path: path.join(__dirname, 'build'),
        publicPath: '/',
        filename: 'index.js'
    },

    node: {
        // Need this when working with express, otherwise the build fails
        __dirname: false,   // if you don't put this is, __dirname
        __filename: false,  // and __filename return blank or /
    },

    resolve: {
        extensions: ['.ts', '.tsx', '.js'],
    },

    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: [
                    'ts-loader',
                ]
            }
        ]
    },

    externals: [ nodeExternals() ]
}

My admin.options.js

import AdminBro, { AdminBroOptions } from 'admin-bro';

// @ts-ignore
import AdminBroMongoose from 'admin-bro-mongoose';

AdminBro.registerAdapter(AdminBroMongoose);
import Customer from './customers/customer.schema';

const options: AdminBroOptions = {
    resources: [Customer],
    dashboard: {
        component: AdminBro.bundle('../src/components/Dashboard/Dashboard.tsx'),
    }
};

export default options;
wojtek-krysiak commented 4 years ago

@BeaRRRRR what is the error in the console?

mtjn commented 4 years ago

Hey, I am using tsc to compile my app, and getting this error even with the workaround suggested. I am building the app using tsc and running with node build

Using either path references doesn't make a difference: "../../../admin/resources/customResource/components/CustomComponent.tsx" or "./components/CustomComponent.tsx"

Code

./admin/resources/customResource/index.ts

import AdminBro from "admin-bro";

import db from "../../../models/";

export default {
  resource: db.sequelize.models.resource,
  options: {
    properties: {
      customProperty: {
        components: {
          show: AdminBro.bundle(
            "../../../admin/resources/customResource/components/CustomComponent.tsx"
          ),
        },
      },
    },
  },
};

./tsconfig.json

{
  "compileOnSave": true,
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "lib": [ "es6" ],
    "module": "commonjs",
    "moduleResolution": "node",
    "outDir": "build",
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "es6"
  },
  "include": [
    "**/*.ts",
    "**/*.js"
  ],
  "exclude": [
    "node_modules",
    "build"
  ]
}

./package.json

{
  "engines": {
    "node": ">=10.x"
  },
  "main": "index.js",
  "scripts": {
    "start": "node build",
    "build": "tsc"
  },
  "dependencies": {
    "@babel/preset-env": "^7.9.5",
    "@types/express": "^4.17.2",
    "@typescript-eslint/eslint-plugin": "^2.29.0",
    "@typescript-eslint/parser": "^2.29.0",
    "admin-bro": "^2.3.0",
    "admin-bro-expressjs": "^2.0.5",
    "admin-bro-sequelizejs": "^0.4.2",
    "dotenv": "^8.1.0",
    "eslint": "^6.1.0",
    "eslint-config-prettier": "^6.0.0",
    "eslint-plugin-jest": "^23.6.0",
    "eslint-plugin-prettier": "^3.1.0",
    "express": "^4.17.1",
    "express-formidable": "^1.2.0",
    "express-session": "^1.17.1",
    "express-slow-down": "^1.3.1",
    "react": "^16.13.1",
    "sequelize": "^5.21.7",
    "ts-node-dev": "^1.0.0-pre.44",
    "typescript": "^3.8.3"
  }
}

Error output:

$ node build
/home/USER/APP/node_modules/admin-bro/lib/admin-bro.js:375
      throw new _configurationError.default(`Given file "${src}", doesn't exist.`, 'AdminBro.html');
      ^

ConfigurationError:
    Given file "../../../admin/resources/customResource/components/CustomComponent.tsx", doesn't exist.
    More information can be found at: https://softwarebrothers.github.io/admin-bro-dev/AdminBro.html

    at Function.bundle (/home/USER/APP/node_modules/admin-bro/lib/admin-bro.js:375:13)
    at Object.<anonymous> (/home//USER/APP/build/adminBro/resources/business/index.js:28:47)
    at Module._compile (internal/modules/cjs/loader.js:936:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:947:10)
    at Module.load (internal/modules/cjs/loader.js:790:32)
    at Function.Module._load (internal/modules/cjs/loader.js:703:12)
    at Module.require (internal/modules/cjs/loader.js:830:19)
    at require (internal/modules/cjs/helpers.js:68:18)
    at Object.<anonymous> (/home/michael/apps/admin/build/adminBro/index.js:22:36)
    at Module._compile (internal/modules/cjs/loader.js:936:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:947:10)
    at Module.load (internal/modules/cjs/loader.js:790:32)
    at Function.Module._load (internal/modules/cjs/loader.js:703:12)
    at Module.require (internal/modules/cjs/loader.js:830:19)
    at require (internal/modules/cjs/helpers.js:68:18)
    at Object.<anonymous> (/home/michael/apps/admin/build/index.js:10:20)
    at Module._compile (internal/modules/cjs/loader.js:936:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:947:10)
    at Module.load (internal/modules/cjs/loader.js:790:32)
    at Function.Module._load (internal/modules/cjs/loader.js:703:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:999:10)
    at internal/main/run_main_module.js:17:11 {
  name: 'ConfigurationError'
}
error Command failed with exit code 1.
wojtek-krysiak commented 4 years ago

can you check out the path for the file in ./.adminbro/entry.js - is it correct? - this is what AdminBro generates

wojtek-krysiak commented 4 years ago

also:

mtjn commented 4 years ago

I found the problem - I hadn't added .tsx to the include statement in tsconfig.json. Fixing that resolves the issue completely, I can also use the normal relative path without needing to traverse back to the project root.

Sorry for the unrelated report, appreciate your help.

wojtek-krysiak commented 4 years ago

np - but bear in mind that in production (when you will build your src first and then launch with regular node command you will have to have all paths pointing to the original .tsx files (traverse back will be required)

talkl commented 3 years ago

can you check out the path for the file in ./.adminbro/entry.js - is it correct? - this is what AdminBro generates

  1. this is a common error please have a look. you can maybe add in the package.json prestart: rm -rf .adminbro

  2. about typescript resolution of the file path. how about this: import * as path from "path"; path.dirname(require.main.filename) + "/YOUR/RELATIVE/PATH_FROM/INDEX/YourReactComponent";

Xabibax commented 3 years ago

Another way to do it, is to compile your tsx with "react": "preserve" and to targer ESNext (to generate jsx and keep export default Component) and then compile every ts files (except tsx of course) with your normal tsconfig. Something like that : tsc -p tsconfigTSX.json tsc -p tsconfig.json

It did the trick to me.