microsoft/TypeScript-React-Starter

import images

StokeMasterJack opened this issue ยท 25 comments

This works with stock create-react-app:

import logo from "./ss-logo-transparent.png";

This may be related:
https://stackoverflow.com/a/36151803/1064584
C&P the answer:

The problem is that you confuse TypeScript level modules and Webpack level modules.
In Webpack any file that you import goes through some build pipeline.
In Typescript only .ts and .js files are relevant and if you try to import x from file.png TypeScript just does not know what to do with it, webpack config is not used by TypeScript.
In your case you need to separate the concerns, use import from for TypeScript/EcmaScript code and use require for Webpack specifics.
You would need to make Typescipt ignore this special Webpack require syntax with a definition like this in a .d.ts file:
declare function require(string): string;
This will make Typescript ignore the require statements and Webpack will be able to process it in the build pipeline.

All you have to do is add

declare module '*.png'

to a .d.ts file included in your build, and you can use the regular import syntax, no need for require.

There is two things that must be done. The first one got identified by pelotom which is to specify to TypeScript how to handle the PNG file. However, the original post contains a syntax that was not working for me. I had to use the star/import syntax.

To summarize
1: Create a file with : declare module '*.png'
2 Import with *: import * as logo from "./ss-logo-transparent.png";

Type '{ src: typeof "*.png"; alt: "logo"; }' is not assignable to type 'HTMLProps<HTMLImageElement>'. Types of property 'src' are incompatible. Type 'typeof "*.png"' is not assignable to type 'string | undefined'. Type 'typeof "*.png"' is not assignable to type 'string'.

Even i tried to export default as string still not satisfy it

@midori0507 How are you passing the src to your image?

It should be similar to :

import * as logo from "./logo.png";
<img src={logo}  /> 

Having :
1:

declare module "*.png" {
  const content: string;
  export default content;
}

2: Import with *: import * as logo from "./ss-logo-transparent.png";
3:

import * as logo from "./logo.png";
<img src={logo}  />

4: I sill get Type 'typeof "*.png"' is not assignable to type 'string'.

stunaz: the way you have it declared is as a default export only, so you would need to use it as

import logo from "./logo.png";

@pelotom spoke too soon, not working like that. I got undefined. With * as , I have my path to the image. but the typescript is complaining as shown above
Does the loader matters? Right now I am using url-loader

ok got rid of typescript warning by removing

{
  const content: string;
  export default content;
}

Just having declare module '*.png' works

I tried everything but it is not working. Is it because of my loaders?

const sharedConfig = () => ({
    stats: { modules: false },
    resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
    output: {
        filename: '[name].js',
        publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
    },
    module: {
        rules: [
            { test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
            { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
        ]
    },
    plugins: [new CheckerPlugin()]
});

I've fixed this problem in this way:

declare module "*.png" {
    const value: any;
    export = value;
}
/// <reference path='./index.d.ts'/>

import  * as logoImg from "../../assets/images/logo.png";

<img src={logoImg} />

Thanks @pelotom
This seems like black magic to me.. Is there any explanation anywhere of why this works?

@pelotom or @damiangreen
My problem is that the image files do not get copied over to the outdir. What setup are you using to ensure that those images exist in the outdir?

I created an answer on SO outlining a couple of options: https://stackoverflow.com/a/49715468/4216035

Do not work for me. Have i missed something? Have i messed up with folders structure/absolute|relative paths?
I've added import-jpg.d.ts and do not have ts-loader errors, but still can't import image. But i don't have any issues with using same image in css as background-image (i have css modules).
in case when i use const image = require('./large-image.jpg') it works just fine.

webpack does see this particular image during build process

Built at: 4/14/2018 5:43:07 PM
                               Asset       Size  Chunks             Chunk Names
d2281ac61e357b25714ab2a6d357291e.jpg    947 KiB          [emitted]
                      main.bundle.js   2.82 MiB    main  [emitted]  main
                          index.html  178 bytes          [emitted]
Entrypoint main = main.bundle.js
import image from './large-image.jpg';

console.log('----------');
console.log('image');
console.log(image); //undefined here
console.log('----------');

export default class CssModules extends React.Component<{}, {}> {
  render() {
    return (
      <section className={styles.root}>
        <div className={styles.largeImageBg} />
        <img className={styles.largeImageImage} src={image} alt="" />
      </section>
    );
  }
}

i'm using webpack-url-loader and have next project structure:

project
|- /config
  |- /webpack
    |- common.webpack.config.js
  |- tsconfig.json

|- /src
  |- /components
    |- /component
      |- styles.css
      |- index.tsx
      |- large-image.jpg

|- /typings
  |- import-jpg.d.ts

here is the gist with files content

solved:
desired options is "esModuleInterop": true

i use "typescript": "^2.8.1"

i think i found reason of issue
when i use

declare module '*.jpg' {
  const fileName: string;
  export default fileName;
}

and then

import image from './large-image.jpg';

i get next picture in my compiled code
2018-04-15_1402
it is compiled to

var large_image_jpg_1 = __webpack_require__(4);
console.log(large_image_jpg_1.default);

large_image_jpg_1 becomes string - image url.
and string.default is undefined
but if i use

declare module '*.jpg' {
  const fileName: string;
  export = fileName;
}

and then

import * as image from './large-image.jpg';

it works fine.

i've seen this issue, but allowSyntheticDefaultImports did not help.

any thoughts?

For anyone using Parcel, this works:

  1. index.d.ts: declare module '*.png'; (no need for the content or value fun)
  2. index.tsx: /// <reference path='./index.d.ts'/> (props to @redredredredredred)
  3. index.tsx: import logo from "./logo.png"; (no need for *)
  4. <img src={logo} /> or <div style={``background: url(${logo});``}>โ€ฆ</div> (dunno how to escape backticks in inline code)

One problem I am having with the approach @pelotom suggested is that TypeScript will not check if the file imported actually exists.

import image from '../this/path/is/wrong.jpg'; will not fail on compile, but will cause a crash on runtime. Is there any workaround for this?

For some reason tsc only checks the path in require('../wrong/file.jpg') if the file is .js (not .ts).

the following worked for me:

  1. use "module": "commonjs" inside tsconfig.json (get access to older libraries)
  2. use declare module '*.png'; in typings.d.ts
  3. use import image = require('./the/image.png');
  4. use <img src={image} />

@pelotom @midori0507 @redredredredredred

So....
Does this code snippet

declare module "*.png" {
    const value: any;
    export = value;
}

Have a file it's meant to be in and a location for that file?

thanks.

ww-k commented

I've fixed this problem in this way:

declare module "*.png" {
    const value: any;
    export = value;
}
/// <reference path='./index.d.ts'/>

import  * as logoImg from "../../assets/images/logo.png";

<img src={logoImg} />

this work for me.

But add /// <reference path='...'/> in every ts file is ugly.
Is there a pretty way ?
I had tried add typeRoots in tsconfig.json, and it's not work.

project architecture

|- /src
  |- /components
    |- /component
      |- index.less
      |- index.tsx
      |- large-image.jpg

|- /typings
  |- index.d.ts

|- tsconfig.json

tsconfig.json

{
    "compilerOptions": {
        "outDir": "./ts_built",
        "typeRoots": [
            "./node_modules/@types",
            "./typings"
        ],
        "sourceMap": true,
        "noImplicitAny": false,
        "allowJs": true,
        "moduleResolution": "node",
        "module": "commonjs",
        "target": "es2015",
        "jsx": "react",
    },
    "include": [
        "./src/**/*"
    ]
}

typings/index.d.ts

// declare webpack modules
declare module '*.png'
declare module '*.jpg'
declare module '*.gif'
declare module '*.less'

kabua commented

@ww-k I had the same question. Here's what worked for me. Inside your tsconfig.json file add the following:

  "includes" :[
    "./typings/**/*.d.ts"
  ],
ww-k commented

@kabua thank you. It seems like include config prevent ts load .d.ts files which in compilerOptions.typeRoots and not in include

All you have to do is add

declare module '*.png'

to a .d.ts file included in your build, and you can use the regular import syntax, no need for require.

I have too many ".d.ts" files in my project. Which one to use ?

@AnshulBasia pretty much any of it. But better some root .d.ts like globals.d.ts

Btw, how did I solve it:

  1. Added this to my globals.d.ts
declare module '*.png' {
  const content: string
  export default content
}
  1. Added this to my tsconfig.json
`{ "compilerOptions": { "esModuleInterop": true } }`

So imports like this works perfectly:

import image from './my_image.png'

P.S. I feel like this is still just a workaround, these png/etc files should be supported out of the box