"Cannot Use Import Outside of a Module" in VS Code Notebook Extension

31 views Asked by At

I'm learning how to use VS Code Notebook API. I'm trying to build a single extension that contributes to notebooks and notebook renderers. While debugging the extension, I'm getting "Cannot Use Import Outside of a Module" error which is, as I suppose, connected with TSC/Webpack magic.

Steps to Reproduce

  1. Create a "New Notebook Renderer (TypeScript)" extension
yo code --extensionType=notebook
  1. Open src/extension/extension.ts

  2. Add import { TextEncoder } from 'util';

  3. Add new TextEncoder(); in activate function body

  4. Click "Start Debugging"

enter image description here

As I understand, the error is caused by

import{createRequire as e}from"module";

which gets generated in out/extension/extension.js when there is a reference to a class from another module.

My Attempt to Solve the Problem

  1. Open webpack.config.js

  2. Remove

experiments: {
    outputModule: true,
},
  1. Replace
makeConfig(argv, { entry: './src/client/index.ts', out: './out/client/index.js', target: 'web', library: 'module' }),

with

makeConfig(argv, { entry: './src/client/index.ts', out: './out/client/index.js', target: 'web', library: 'commonjs' }),
  1. Run npm run compile

  2. Click "Start Debugging"

The previous error should disappear. Now there is another one

enter image description here

My further attempts to include var exports = {}; somewhere in build artifacts cause even more problems.

Do you know how to fix the error? I don't want to split the extension into two separate parts.

2

There are 2 answers

0
Mike Lischke On

The main problem is that VS Extensions use Node.js and that requires (at least currently) code to be CommonJS. By adding an import statement, you are trying to use ESM syntax (CommonJS uses require). You have now three options to solve this:

  1. Use require instead of import.
  2. Use dynamic imports.
  3. Make your extension using ESM and use a bundler like webpack or esbuild to generate a CJS bundle for your extension.
0
Kaksisve On

I've tried to configure TypeScript and Webpack myself and it have solved the issue.

Here is the TypeScript configuration for renderer part.

{
  "compilerOptions": {
    "module": "CommonJS",
    "target": "ES2019",
    "lib": ["ES2019", "DOM"],
    "types": ["webpack-env", "vscode-notebook-renderer"],
    "moduleResolution": "Node",
    "sourceMap": true,
    "strict": true,
    "rootDir": ".",
    "outDir": "../../dist/client",
    "noEmit": true
  },
  "references": []
}

Here is the TypeScript configuration for rest of the notebook.

{
  "compilerOptions": {
    "module": "CommonJS",
    "target": "ES2019",
    "lib": ["ES2019"],
    "types": ["node"],
    "moduleResolution": "Node",
    "sourceMap": true,
    "strict": true,
    "rootDir": ".",
    "outDir": "../../dist/extension"
  },
  "references": []
}

And here is the Webpack configuration.

// @ts-check

'use strict';

// eslint-disable-next-line @typescript-eslint/naming-convention
const path = require('path');
const webpack = require('webpack');

/**
 * @type {webpack.Configuration}
 */
const extensionConfig = {
  target: 'node',
  mode: 'none',
  entry: './src/extension/index.ts',
  output: {
    path: path.resolve(__dirname, 'dist', 'extension'),
    filename: 'index.js',
    libraryTarget: 'commonjs'
  },
  externals: {
    vscode: 'commonjs vscode'
  },
  resolve: {
    extensions: ['.js', '.ts']
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
      },
    ]
  },
  devtool: 'nosources-source-map',
  infrastructureLogging: {
    level: 'log',
  },
};

/**
 * @type {webpack.Configuration}
 */
const clientConfig = {
  target: 'web',
  mode: 'none',
  entry: './src/client/index.ts',
  output: {
    path: path.resolve(__dirname, 'dist', 'client'),
    filename: 'index.js',
    publicPath: '',
    libraryTarget: 'module'
  },
  experiments: {
    outputModule: true,
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
        options: {
          configFile: path.join('./src/client', 'tsconfig.json'),
          transpileOnly: true,
          compilerOptions: {
            noEmit: false,
          },
        }
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              modules: true
            },
          },
        ],
      },
    ]
  },
  externals: {
    vscode: 'commonjs vscode'
  },
  plugins: [
    new webpack.ProvidePlugin({
      process: 'process/browser',
    }),
    new webpack.DefinePlugin({
      __webpack_relative_entrypoint_to_root__: JSON.stringify(
        path.posix.relative(path.posix.dirname(`/index.js`), '/'),
      ),
      scriptUrl: 'import.meta.url',
    }),
  ],
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx']
  },
  devtool: 'inline-source-map',
  infrastructureLogging: {
    level: 'log',
  },
};

module.exports = [
  extensionConfig,
  clientConfig,
];