Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a sample rollup config for typescript #1095

Closed
NullVoxPopuli opened this issue Jan 31, 2022 · 7 comments
Closed

Provide a sample rollup config for typescript #1095

NullVoxPopuli opened this issue Jan 31, 2022 · 7 comments

Comments

@NullVoxPopuli
Copy link
Collaborator

NullVoxPopuli commented Jan 31, 2022

Updated

I think I got everything working output wise.
I still have the issue where the consuming app is looking for dist/addon, but I can debug that.

Here is my current config which appears to generate things in a way @embroider/addon-dev is expecting:

  • automatic ember-addon.app-js updates
  • app-re-exports in dist/_app_

Notes

  • babel config at ./config/babel.config.js
  • rollup config at ./config/rollup.config.js
  • build command in package.json: "build:js": "rollup -c ./config/rollup.config.js",
  • builds type declarations as well as the JS -- and these combined are faster than running tsc by itself (somehow)
// @ts-nocheck
import path from 'path';

import alias from '@rollup/plugin-alias';
import multiInput from 'rollup-plugin-multi-input';
import ts from 'rollup-plugin-ts';
import { defineConfig } from 'rollup';
import { nodeResolve } from '@rollup/plugin-node-resolve';

import { Addon } from '@embroider/addon-dev/rollup';

import babelConfig from './babel.config';

const addon = new Addon();

const extensions = ['.js', '.ts'];

const transpilation = [
 // Instruct rollup how to resolve ts and hbs imports
 // (importing a template-only component, for example)
 nodeResolve({ resolveOnly: ['./'], extensions: [...extensions, '.hbs'] }),

 // Allow top-level imports (what folks are used to from v1 addons)
 // During the build, anything referencing a top-level import will be
 // replaced with a relative import.
 // DANGER: it's somewhat easy to cause circular references with this tool
 alias({
   entries: [
     {
       find: '#types',
       replacement: path.resolve('src', '-private', 'types.ts'),
     },
     {
       find: '<my-addon>',
       replacement: path.resolve('src'),
     },
     {
       find: '<my-addon>/(.*)',
       replacement: path.resolve('src/$1'),
     },
   ],
 }),

 // This babel config should *not* apply presets or compile away ES modules.
 // It exists only to provide development niceties for you, like automatic
 // template colocation.
 // See `babel.config.json` for the actual Babel configuration!
 // babel({ babelHelpers: 'bundled', extensions }),
 ts({
   // can be changed to swc or other transpilers later
   // but we need the ember plugins converted first
   // (template compilation and co-location)
   transpiler: 'babel',
   browserslist: false,
   babelConfig,
   // setting this true greatly improves performance, but
   // at the cost of safety.
   transpileOnly: false,
   tsconfig: {
     fileName: 'tsconfig.json',
     hook: (config) => ({ ...config, declaration: true, declarationDir: 'declarations' }),
   },
 }),

 // Follow the V2 Addon rules about dependencies. Your code can import from
 // `dependencies` and `peerDependencies` as well as standard Ember-provided
 // package names.
 addon.dependencies(),

 // Ensure that standalone .hbs files are properly integrated as Javascript.
 addon.hbs(),

 // addons are allowed to contain imports of .css files, which we want rollup
 // to leave alone and keep in the published output.
 // addon.keepAssets(['**/*.css']),
];

// these should be JS, even though the authored format is TS
const globallyAvailable = ['components/**/*.js', 'services/*.js'];

export default defineConfig({
 input: ['src/**/*{js,hbs,ts}'],
 output: {
   format: 'es',
   dir: 'dist',
 },
 plugins: [
   multiInput(),

   ...transpilation,
   // These are the modules that users should be able to import from your
   // addon. Anything not listed here may get optimized away.
   addon.publicEntrypoints([...globallyAvailable]),

   // These are the modules that should get reexported into the traditional
   // "app" tree. Things in here should also be in publicEntrypoints above, but
   // not everything in publicEntrypoints necessarily needs to go here.
   addon.appReexports([...globallyAvailable]),

   addon.clean(),
 ],
});

Original question / set of problems

Related to: #1094

I have this so far, but certain things don't work:

// ./config/rollup.config.js
import path from 'path';

import alias from '@rollup/plugin-alias';
import { babel } from '@rollup/plugin-babel';
import { defineConfig } from 'rollup';
import { nodeResolve } from '@rollup/plugin-node-resolve';

import { Addon } from '@embroider/addon-dev/rollup';

const addon = new Addon({
 srcDir: 'src',
 destDir: 'dist',
});

const extensions = ['.js', '.ts', '.hbs'];

const transpilation = [
 // Instruct rollup how to follow import paths for typescript files
 // (in JS-only libraries, this isn't needed)
 nodeResolve({ resolveOnly: ['./'], extensions }),

 // Allow top-level imports (what folks are used to from v1 addons)
 // During the build, anything referencing a top-level import will be
 // replaced with a relative import.
 // DANGER: it's somewhat easy to cause circular references with this tool
 alias({
   entries: [
     {
       find: '#types',
       replacement: path.resolve('src', '-private', 'types.ts'),
     },
     {
       find: '<addon-name>',
       replacement: path.resolve('src'),
     },
     {
       find: '<addon-name>/(.*)',
       replacement: path.resolve('src/$1'),
     },
   ],
 }),

 // This babel config should *not* apply presets or compile away ES modules.
 // It exists only to provide development niceties for you, like automatic
 // template colocation.
 // See `babel.config.json` for the actual Babel configuration!
 babel({ babelHelpers: 'bundled', extensions }),
 //
 // Follow the V2 Addon rules about dependencies. Your code can import from
 // `dependencies` and `peerDependencies` as well as standard Ember-provided
 // package names.
 addon.dependencies(),

 // Ensure that standalone .hbs files are properly integrated as Javascript.
 addon.hbs(),

 // addons are allowed to contain imports of .css files, which we want rollup
 // to leave alone and keep in the published output.
 // addon.keepAssets(['**/*.css']),
];

const globallyAvailable = ['src/components/**/*.{js,ts}', 'src/services/*.{js,ts}'];

export default [
 defineConfig({
   input: 'src/index.ts',
   output: { ...addon.output(), entryFileNames: 'index.js' },
   plugins: [
     ...transpilation,
     // These are the modules that users should be able to import from your
     // addon. Anything not listed here may get optimized away.
     addon.publicEntrypoints([...globallyAvailable]),

     // These are the modules that should get reexported into the traditional
     // "app" tree. Things in here should also be in publicEntrypoints above, but
     // not everything in publicEntrypoints necessarily needs to go here.
     addon.appReexports(globallyAvailable),
   ],
 }),
 defineConfig({
   input: 'src/test-support/index.ts',
   output: { ...addon.output(), entryFileNames: 'test-support.js' },
   plugins: [
     ...transpilation,
     // These are the modules that users should be able to import from your
     // addon. Anything not listed here may get optimized away.
     addon.publicEntrypoints(['test-support/index.js']),
   ],
 }),
];
Babel config
// ./babel.config.js
import { createRequire } from 'module';

const require = createRequire(import.meta.url);
const { resolve } = require;

export default {
  plugins: [
    [
      resolve('@babel/plugin-transform-typescript'),
      {
        allowDeclareFields: true,
        onlyRemoveTypeImports: true,
        // Default enums are IIFEs
        optimizeConstEnums: true,
      },
    ],
    [
      resolve('@babel/plugin-proposal-decorators'),
      {
        // The stage 1 implementation
        legacy: true,
      },
    ],
    [
      resolve('@babel/plugin-proposal-class-properties'),
      {
        // Only support browsers that also support class properties...
        // If all addons do this, it greatly reduces shipped JS
        loose: true,
      },
    ],
    resolve('@embroider/addon-dev/template-colocation-plugin'),
  ],
};
relevant package.json entries
  "type": "module",
  "main": "dist/index.js",
  "types": "declarations",
  "exports": {
    ".": "./dist/index.js",
    "./test-support.js": "./dist/test-support.js",
    "./addon-main.js": "./addon-main.cjs"
  },
  "files": [
    "dist",
    "addon-main.cjs",
    "declarations",
    "CHANGELOG.md",
    "README.md"
  ],

  "dependencies": {
    "@embroider/addon-shim": "^1.0.0"
  },

  "ember-addon": {
    "version": 2,
    "type": "addon",
    "main": "addon-main.cjs",
    "app-js": {}
  },

And the command I'm running:

rollup -c ./config/rollup.config.js --watch --no-watch.clearScreen

And a sample of the output dist/index.js:

var _MyComponent = setComponentTemplate(_MyComponent, hbs`{{#let (get @data @valuePath) as |value|}}
  {{value}}
{{/let}}`);

I did try rollup-plugin-ts, however, it has similar problems:

  • error about babel can only work with ESM configs in async mode (implying rollup-plugin-ts uses babel synchronously)
  • even with the babel config changed to CJS (or imported and set as babelConfig), hbs files are not properly converted to javascript
@NullVoxPopuli
Copy link
Collaborator Author

NullVoxPopuli commented Jan 31, 2022

ah for babel I needed to use @glimmer/compiler.

I forgot about that.

import { createRequire } from 'module';
import { precompile } from '@glimmer/compiler';

const require = createRequire(import.meta.url);
const { resolve } = require;

export default {
  plugins: [
    [
      resolve('@babel/plugin-transform-typescript'),
      {
        allowDeclareFields: true,
        onlyRemoveTypeImports: true,
        // Default enums are IIFEs
        optimizeConstEnums: true,
      },
    ],
    [
      resolve('@babel/plugin-proposal-decorators'),
      {
        // The stage 1 implementation
        legacy: true,
      },
    ],
    [
      resolve('@babel/plugin-proposal-class-properties'),
      {
        // Only support browsers that also support class properties...
        // If all addons do this, it greatly reduces shipped JS
        loose: true,
      },
    ],
    [
      resolve('babel-plugin-ember-template-compilation'),
      {
        precompile,
        enableLegacyModules: ['ember-cli-htmlbars'],
      },
    ],
    resolve('@embroider/addon-dev/template-colocation-plugin'),
  ],
};

So now all that's left are all the other issues. lol

@josemarluedke
Copy link
Collaborator

josemarluedke commented Feb 1, 2022

Totally support this idea. I haven't tried to have an addon v2 with hbs and TS files, but I did with glimmer-apollo which is TS files only.

I suggest using rollup-plugin-ts which allows to use babel or swc. It also generates declaration files, which is important to ship addons with them.

@NullVoxPopuli
Copy link
Collaborator Author

Yeah, my issues with that plugin are no more, as my issue was actually the missing babel plugin. Just need better extensions support from addon-dev now

@josemarluedke
Copy link
Collaborator

@NullVoxPopuli Can you post the working example using rollup-plugin-ts?

@NullVoxPopuli
Copy link
Collaborator Author

// @ts-nocheck
import path from 'path';

import alias from '@rollup/plugin-alias';
import ts from 'rollup-plugin-ts';
import { babel } from '@rollup/plugin-babel';
import { defineConfig } from 'rollup';
import { nodeResolve } from '@rollup/plugin-node-resolve';

import { Addon } from '@embroider/addon-dev/rollup';

import babelConfig from './babel.config';

const addon = new Addon({
  srcDir: 'src',
  destDir: 'dist',
});

const extensions = ['.js', '.ts'];

const transpilation = [
  // Instruct rollup how to follow import paths for typescript files
  // (in JS-only libraries, this isn't needed)
  nodeResolve({ resolveOnly: ['./'], extensions: [...extensions, '.hbs'] }),

  // Allow top-level imports (what folks are used to from v1 addons)
  // During the build, anything referencing a top-level import will be
  // replaced with a relative import.
  // DANGER: it's somewhat easy to cause circular references with this tool
  alias({
    entries: [
      {
        find: '#types',
        replacement: path.resolve('src', '-private', 'types.ts'),
      },
      {
        find: <my-addon>',
        replacement: path.resolve('src'),
      },
      {
        find: '<my-addon>/(.*)',
        replacement: path.resolve('src/$1'),
      },
    ],
  }),

  // This babel config should *not* apply presets or compile away ES modules.
  // It exists only to provide development niceties for you, like automatic
  // template colocation.
  // See `babel.config.json` for the actual Babel configuration!
  // babel({ babelHelpers: 'bundled', extensions }),
  ts({
    // can be changed to swc or other transpilers later
    // but we need the ember plugins converted first
    // (template compilation and co-location)
    transpiler: 'babel',
    browserslist: false,
    babelConfig,
    tsconfig: {
      fileName: 'tsconfig.json',
      hook: (config) => ({ ...config, declaration: true }),
    },
  }),

  // Follow the V2 Addon rules about dependencies. Your code can import from
  // `dependencies` and `peerDependencies` as well as standard Ember-provided
  // package names.
  addon.dependencies(),

  // Ensure that standalone .hbs files are properly integrated as Javascript.
  addon.hbs(),

  // addons are allowed to contain imports of .css files, which we want rollup
  // to leave alone and keep in the published output.
  // addon.keepAssets(['**/*.css']),
];

const globallyAvailable = ['src/components/**/*.{js,ts}', 'src/services/*.{js,ts}'];

export default [
  defineConfig({
    input: 'src/index.ts',
    output: { ...addon.output(), entryFileNames: 'index.js' },
    plugins: [
      ...transpilation,
      // These are the modules that users should be able to import from your
      // addon. Anything not listed here may get optimized away.
      addon.publicEntrypoints([...globallyAvailable]),

      // These are the modules that should get reexported into the traditional
      // "app" tree. Things in here should also be in publicEntrypoints above, but
      // not everything in publicEntrypoints necessarily needs to go here.
      addon.appReexports(globallyAvailable),
    ],
  }),
  defineConfig({
    input: 'src/test-support/index.ts',
    output: { ...addon.output(), entryFileNames: 'test-support.js' },
    plugins: [
      ...transpilation,
      // These are the modules that users should be able to import from your
      // addon. Anything not listed here may get optimized away.
      addon.publicEntrypoints(['test-support/index.js']),
    ],
  }),
];

@NullVoxPopuli
Copy link
Collaborator Author

Ok, so the above doesn't work with components/services. I'm going to update the original post for easier discoverability with the working config that I have (it's an oofta)

@NullVoxPopuli
Copy link
Collaborator Author

Closing, as I think we have a solid option for TS now -- gonna throw a PR up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants