從零開始配置TypeScript + React + React-Router + Redux + Webpack開發環境

轉載請註明出處!php

說在前面的話:

一、爲何不使用現成的腳手架?腳手架配置的東西太多過重了,一股腦全塞給你,我只想先用一些我能懂的庫和插件,而後慢慢的添加其餘的。並且本身從零開始配置也能學到更多的東西不是麼。css

二、教程只配置了開發環境,並無配置生產環境。html

三、教程針對人羣是有過React + Redux經驗,而且想在新項目中使用TypeScript的人(或者是想本身從零開始配置開發環境的)前端

四、由於前端發展突飛猛進,今天能用的配置到明天可能就不能用了(好比React-Router就有V4了,並且官方說是徹底重寫的),因此本文中安裝的包都是指定版本的。node

五、教程遵循最小可用原則,因此能不用的庫和插件就沒用(主要是會的就沒多少,怕用出問題,逃~~)。react

六、基於5,因此教程不會一開始就把全部東西全裝上,會一步一步慢慢來。webpack

六、教程在macOS下完成,win環境系可能會有一些其餘的問題。git

 

初始環境

node版本爲6.9.0es6

 

初始化項目

 建立並進入項目github

mkdir demo && cd demo

初始化項目

npm init

 

安裝初始依賴

首先是安裝webpack和webpack-dev-server(全局安裝過的請忽略)

npm i -D webpack@3.6.0

而後安裝React和Types中React的聲明文件

npm i --S react@15.5.4 react-dom@15.5.4 @types/react@15.6.0 @types/react-dom@15.5.0

上面@types開頭的包都是typeScript的聲明文件,你能夠進入node_modules/@types/XX/index.d.ts進行查看。

關於聲明文件的具體介紹能夠在github上的DefinitelyTyped庫看到。

接下來安裝TypeScript,ts-loader和source-map-loader

npm i -D typescript@2.5.3 ts-loader@2.3.7 source-map-loader@0.2.2

ts-loader可讓Webpack使用TypeScript的標準配置文件tsconfig.json編譯TypeScript代碼。

source-map-loader使用任意來自Typescript的sourcemap輸出,以此通知webpack什麼時候生成本身的sourcemaps。 這讓你在調試最終生成的文件時就好像在調試TypeScript源碼同樣。

 

添加TypeScript配置文件 

咱們須要一個tsconfig.json文件來告訴ts-loader如何編譯代碼TypeScript代碼。

在當前根目錄下建立tsconfig.json文件,並添加以下內容:

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "jsx": "react"
  },
  "include": [
    "./src/**/*"
  ]
}

outDir:輸出目錄。

sourceMap:把 ts 文件編譯成 js 文件的時候,同時生成對應的sourceMap文件。

noImplicitAny:若是爲true的話,TypeScript 編譯器沒法推斷出類型時,它仍然會生成 JavaScript 文件,可是它也會報告一個錯誤。爲了找到錯誤仍是設置爲true比較好。

module:代碼規範,也能夠選amd。

target:轉換成es5

jsx:TypeScript具備三種JSX模式:preservereactreact-native。 這些模式只在代碼生成階段起做用 - 類型檢查並不受影響。 在preserve模式下生成代碼中會保留JSX以供後續的轉換操做使用(好比:Babel)。 另外,輸出文件會帶有.jsx擴展名。 react模式會生成React.createElement,在使用前不須要再進行轉換操做了,輸出文件的擴展名爲.js。 react-native至關於preserve,它也保留了全部的JSX,可是輸出文件的擴展名是.js。咱們這裏由於不會用babel再轉,因此用react就行。

include:須要編譯的目錄。

 

寫些代碼

首先建立目錄

mkdir src && cd src
mkdir components && cd components

在此文件夾下添加一個Hello.tsx文件,代碼以下:

import * as React from 'react';

export interface Props {
  name: string;
  enthusiasmLevel?: number;
}

export default class Hello extends React.Component<Props, object> {
  render() {
    const { name, enthusiasmLevel = 1 } = this.props;

    if (enthusiasmLevel <= 0) {
      throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
      <div className="hello">
        <div className="greeting">
          Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
      </div>
    );
  }
}

function getExclamationMarks(numChars: number) {
  return Array(numChars + 1).join('!');
}

接下來,在src下建立index.tsx文件,代碼以下:

import * as React from "react";
import * as ReactDOM from "react-dom";

import Hello from "./components/Hello";

ReactDOM.render(
  <Hello name="TypeScript" enthusiasmLevel={10} />,
  document.getElementById('root') as HTMLElement
);

 

咱們還須要一個頁面來顯示Hello組件。 在根目錄建立一個名爲index.html的文件,以下:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>demo</title>
</head>

<body>
  <div id="root"></div>
  <script src="./dist/bundle.js"></script>
</body>

</html>

 

編寫webpack配置文件

在根目錄下建立一個名爲webpack.common.config.js文件,並添加一下內容:

module.exports = {
  entry: "./src/index.tsx",
  output: {
    filename: "bundle.js",
    path: __dirname + "/dist"
  },

  devtool: "source-map",

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

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

      { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
    ]
  },

  plugins: [
  ],
};

這裏不作過多的解釋了。基本上有webpack經驗的都看得懂。至於爲何是webpack.common.config.js而不是webpack.config.js。是由於咱們如今要配置的是開發環境,之後須要配置生產環境,因此咱們就須要多個配置文件,而且將這兩個的通用部分放入到webpack.common.config.js

在根目錄下運行一下命令:

webpack --config webpack.common.config.js

而後打開index.html就能看到咱們寫的頁面了。

 

編寫webpack開發環境配置文件

若是是正式作開發,上面的代碼確定是不夠的,咱們須要webpacl-dev-server提供的最基本也是最好用的熱更新功能。

npm i -D webpack-dev-server@2.9.1

在根目錄下建立webpack.dev.config.js,並添加如下配置:

const webpack = require('webpack');
const config = require('./webpack.common.config');
config.devServer = {
  hot: true,
  publicPath: '/dist/'
}
config.plugins.push(new webpack.HotModuleReplacementPlugin());
module.exports = config;

首先須要引入公共的配置,而後在基礎之上進行修改。

devServer就是webpack-dev-server的配置項。

hot:開啓熱更新,開啓熱更新以後,咱們須要在plugins中加入webpack.HotModuleReplacementPlugin來徹底啓用。同時官方文檔中指出,若是在命令中使用--hot來啓動webpack-dev-server的話,就會自動加載這個插件,再也不須要在config.js中進行引入。

關於HMR的相關部分能夠點擊webpack HMR查看。

publicPath:資源目錄,由於webpack-dev-server啓動以後會把編譯後的資源放在內存當中,那這些資源在哪呢?就是在publicPath指定的目錄裏,由於咱們在webpack.common.config.js中配置的output.path是當前目錄的/dist目錄下,爲了避免再去更改根目錄下的index.html文件,因此咱們這裏也設置成/dist/。 這部份內容具體能夠參照詳解Webpack2的那些路徑

 運行命令:

webpack-dev-server --config webpack.dev.config.js

打開網頁,進入localhots:8080就能夠看到咱們的頁面了。打開瀏覽器的開發者工具,在console部分能看到如下兩句提示就說明熱更新啓動成功了,

而後把這部分很長的命令加入到npm scripts。在package.json的scripts下添加 "start": "webpack-dev-server --config webpack.dev.config.js" 

輸入npm start 就能夠開啓咱們的服務了。

 

添加一個簡單的redux(非新手向)

安裝redux的依賴

npm i -S redux@3.7.2 react-redux@5.0.5 @types/react-redux@5.0.6

爲了能體現redux,咱們接下來給咱們的網頁添加兩個按鈕來增長/刪除文字後面的感嘆號。

首先,咱們來建立一個文件來存放store的接口聲明,放入src/types/index.tsx中,代碼以下:

export interface StoreState {
    languageName: string;
    enthusiasmLevel?: number;
}

定義一些常量供action和reducer使用,放入src/constants/index.tsx

export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;


export const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM';
export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;

添加action,放入src/actions/index.tsx

import * as constants from '../constants'

export interface IncrementEnthusiasm {
  type: constants.INCREMENT_ENTHUSIASM;
}

export interface DecrementEnthusiasm {
  type: constants.DECREMENT_ENTHUSIASM;
}

export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

export function incrementEnthusiasm(): IncrementEnthusiasm {
  return {
    type: constants.INCREMENT_ENTHUSIASM
  }
}

export function decrementEnthusiasm(): DecrementEnthusiasm {
  return {
    type: constants.DECREMENT_ENTHUSIASM
  }
}

添加reducer,放入src/reducers/index.tsx

import { EnthusiasmAction } from '../actions';
import { StoreState } from '../types/index';
import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';

export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
  switch (action.type) {
    case INCREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
    case DECREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
  }
  return state;
}

修改一下Hello組件,如下是修改後的代碼:

import * as React from 'react';

export interface Props {
  name: string;
  enthusiasmLevel?: number;
  onIncrement?: () => void;
  onDecrement?: () => void;
}

export default function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
  if (enthusiasmLevel <= 0) {
    throw new Error('You could be a little more enthusiastic. :D');
  }

  return (
    <div className="hello">
      <div className="greeting">
        Hello {name + getExclamationMarks(enthusiasmLevel)}
      </div>
      <div>
        <button onClick={onDecrement}>-</button>
        <button onClick={onIncrement}>+</button>
      </div>
    </div>
  );
}

function getExclamationMarks(numChars: number) {
  return Array(numChars + 1).join('!');
}

此時咱們頁面已經修改爲功了,但點擊沒有反應,由於咱們尚未鏈接到redux的store中。

添加一個container來連接Hello組件,放入src/containers/Hello.tsx中

import Hello from '../components/Hello';
import * as actions from '../actions/';
import { StoreState } from '../types/index';
import { connect, Dispatch } from 'react-redux';

export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
  return {
    enthusiasmLevel,
    name: languageName,
  }
}

export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
  return {
    onIncrement: () => dispatch(actions.incrementEnthusiasm()),
    onDecrement: () => dispatch(actions.decrementEnthusiasm()),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Hello);

建立一個initState,來定義store初始的值,放入/src/store/initState.tsx中

export default {
  enthusiasmLevel: 1,
  languageName: 'TypeScript',
}

建立一個store,放入/src/store/configureStore.tsx中

import { createStore } from 'redux';
import { enthusiasm } from '../reducers/index';
import { StoreState } from '../types/index';
import initState from './initState';
export default function () {
  const store = createStore<StoreState>(enthusiasm, initState);
  return store;
}

最後修改一下入口文件index.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import Hello from './containers/Hello';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';

const store = configureStore();
ReactDOM.render(
  <Provider store={store}>
    <Hello />
  </Provider>,
  document.getElementById('root') as HTMLElement
);

至此,一個簡單的redux就弄好了。能夠點擊按鈕增長/刪除感嘆號了。

可是如今還有不少不完善的地方,好比Hello組件居然是一個函數,再好比reducer居然只有一個(解決這兩個問題的過程當中會有一些bug待咱們解決)。

放心,這些都將在下面的「添加一個夠用的Redux」中解決。

 

添加一個夠用的Redux

很明顯,一個簡單的redux在咱們稍微大一點的開發中是明顯不夠用的。

因此咱們來改寫一下咱們的代碼。

首當其衝的就是咱們的Hello組件。咱們把Hello組件改爲class的形式

export default class Hello extends React.Component<Props, {}> {
  constructor(props: Props) {
    super(props);
  }
  render() {
    const { name, enthusiasmLevel = 1, onIncrement, onDecrement } = this.props;

    if (enthusiasmLevel <= 0) {
      throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
      <div className="hello">
        <div className="greeting">
          Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
        <div>
          <button onClick={onDecrement}>-</button>
          <button onClick={onIncrement}>+</button>
        </div>
      </div>
    );
  }
}

保存,編譯中,而後就報錯了!

------------------------------------------------------------------------------------------------

ERROR in ./src/containers/Hello.tsx
(20,61): error TS2345: Argument of type 'typeof Hello' is not assignable to parameter of type 'ComponentType<{ enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnthusia...'.
Type 'typeof Hello' is not assignable to type 'StatelessComponent<{ enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnt...'.
Type 'typeof Hello' provides no match for the signature '(props: { enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnthusiasm; onDecrement: () => DecrementEnthusiasm; } & { children?: ReactNode; }, context?: any): ReactElement<any>'.

------------------------------------------------------------------------------------------------

趕忙複製,而後google一下,就能找到咱們要的答案TypeScript-React-Starter | Issues#29,從別人的回答來看貌似是一個bug?那咱們就按回答來更改一下咱們的Hello容器

export function mergeProps(stateProps: Object, dispatchProps: Object, ownProps: Object) {
  return Object.assign({}, ownProps, stateProps, dispatchProps);
}

export default connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps)(Hello);

剛改完,還沒等保存,IDE就提醒咱們有一個錯誤:

Property 'assign' does not exist on type 'ObjectConstructor'.

很明顯,是由於Object沒有assign這個方法,有三種解決方式:

一、安裝Object-assign這個npm包,用這個包去替代。

二、在tsconfig.json中把target從"es5"修改成"es6"。

三、在tsconfig.json的compilerOptions中添加屬性"lib": [

"dom",
"es6",
"dom.iterable",
"scripthost"
]
(推薦,感謝大佬同事提供的解決方法)
第三個方法中的lib就是你寫代碼用的語言的標準,若是要用async的話,還能夠加一個es7

而後再次編譯,發現仍是依舊報錯。只不過此次錯誤信息改了:

------------------------------------------------------------------------------------------------

ERROR in ./src/index.tsx
(10,5): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Pick<Props, "name" | "enthusiasmLevel" |...'.
Type '{}' is not assignable to type 'Readonly<Pick<Props, "name" | "enthusiasmLevel" | "onIncrement" | "onDecrement"> & Object>'.
Property 'name' is missing in type '{}'.

------------------------------------------------------------------------------------------------

此次的報錯是在index.tsx中,能夠看到是由於在Hello組件咱們定義的接口中name的屬性是必須傳的,可是在index.tsx中沒有顯示的傳過去。

可是若是你的瀏覽器是chrome並安裝了react插件的話能夠看到編譯後的代碼是有傳的

姑且就當作是一個bug吧,解決方案有兩種,一種是的index.tsx中給Hello容器加上一個name

ReactDOM.render(
  <Provider store={store}>
    <Hello name="123"/>
  </Provider>,
  document.getElementById('root') as HTMLElement
);

這裏就算加上name也仍是直接顯示的store中的name。因此咱們這裏採用這種方式,而且後面加上React-router以後這段代碼就會改掉,就不會有這個問題了。

另外一種是在Hello組件中把name屬性改爲非必要屬性:

export interface Props {
  name?: string;
  enthusiasmLevel?: number;
  onIncrement?: () => void;
  onDecrement?: () => void;
}

這種方式不推薦。

好了,到如今爲止組件更改完成了。

接下來咱們解決多個reducer的問題。

首先咱們把initState的默認值更改一下:

export default {
  demo: {
    enthusiasmLevel: 1,
    languageName: 'TypeScript',
  }
}

固然還有/src/types/index.tsx也要更改:

export interface demo {
  languageName: string;
  enthusiasmLevel?: number;
}
export interface StoreState {
  demo: demo;
}

而後講/src/reducers/index.tsx命名爲demo.tsx,並對內容進行修改:

import { EnthusiasmAction } from '../actions';
import { demo } from '../types/index';
import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';
import initState from '../store/initState';
export function enthusiasm(state: demo = initState.demo, action: EnthusiasmAction): demo {
  switch (action.type) {
    case INCREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
    case DECREMENT_ENTHUSIASM:
      return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
  }
  return state;
}

其實就是把對應的接口類型進行了更改,並給state添加了默認值。

而後新建一個index.tsx文件,並添加如下內容:

import { combineReducers } from 'redux';
import { enthusiasm } from './demo';
const rootReducer = combineReducers({
  demo: enthusiasm
});

export default rootReducer;

相對應的,也須要修改Hello容器中的引用的值:

export function mapStateToProps({ demo: { enthusiasmLevel, languageName } }: StoreState) {
  return {
    enthusiasmLevel,
    name: languageName,
  }
}

 最後修改一下configureStore中的引用的reducer:

import { createStore } from 'redux';
import  reducers  from '../reducers/index';
import { StoreState } from '../types/index';
import initState from './initState';
export default function () {
  const store = createStore<StoreState>(reducers, initState);
  return store;
}

更改完畢,保存。報錯...

------------------------------------------------------------------------------------

ERROR in ./src/store/configureStore.tsx
(6,41): error TS2345: Argument of type 'Reducer<{}>' is not assignable to parameter of type 'Reducer<StoreState>'.
Type '{}' is not assignable to type 'StoreState'.
Property 'demo' is missing in type '{}'.

------------------------------------------------------------------------------------

是否是感受很熟悉,和以前index.tsx中關於Hello組件的報錯幾乎同樣。

這裏也有兩種解決方案,一種是找到configureStore.tsx中的 const store = createStore<StoreState>(reducers, initState); 把 <StoreState> 泛型刪除。

第二種是和以前同樣的,找到/src/types/index.tsx,將 demo: demo; 加上一個?使之變爲非必須的屬性 demo?: demo; 咱們這裏就採用這種方法。

這裏到底是bug仍是其餘什麼緣由,但願有大神能解答。

至此,咱們夠用的redux就已經完成了。

 

添加一個React-Router

須要注意的是,如今react-router已經到了V4版本了,而且官方說這是一個徹底重寫的版本。因此在我不太熟悉的狀況下,保險起見仍是先選擇V3版本,等之後再更新。

安裝依賴

npm i -S react-router@3.0.5 @types/react-router@3.0.5

在src目錄下建立文件routers.tsx,並添加如下內容:

import * as React from 'react';
import { Route, IndexRoute } from 'react-router';
import Hello from './containers/Hello';

export default (
  <Route path="/">
    <IndexRoute component={Hello} />
    <Route path="/demo">
      <IndexRoute component={Hello} />
    </Route>
  </Route>
);

爲了顯示路由的做用,就加了一個demo路徑。

而後在index.tsx中加上咱們的路由

import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
const store = configureStore();
ReactDOM.render(
  <Provider store={store}>
    <Router history={browserHistory} routes={routes} />
  </Provider>,
  document.getElementById('root') as HTMLElement
);

因爲咱們添加的是browserHistory做爲路由,不是hashHistory,因此咱們須要對服務器作一些路由配置才行。至於爲何,請自行搜索,這裏不作說明了。若是不想用過多設置,也能夠直接把browserHistory替換爲hashHistory便可。

這裏咱們的開發服務器就是webpack-dev-server,因此咱們對webpack.dev.congfig.js進行更改:

const webpack = require('webpack');
const config = require('./webpack.common.config');
config.devServer = {
  hot: true,
  publicPath: '/dist/',
  historyApiFallback: {
    index: './index.html'
  },
}
config.plugins.push(new webpack.HotModuleReplacementPlugin());
module.exports = config;

其實就是當服務器找不到路由目錄時將頁面指向index.html便可。

由於更改了配置,因此咱們須要重啓服務器 npm start 

進入localhost:8080/demo

頁面有顯示Hello組件,說明配置成功了。

 

添加React-Router-Redux

這裏一樣因爲React-Router版本大更新的問題,因此也要嚴格控制版本。

安裝依賴

npm i -S react-router-redux@4.0.8 @types/react-router-redux@4.0.48

更改index.tsx代碼以下:

import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import { syncHistoryWithStore } from 'react-router-redux';

const store = configureStore();
const history = syncHistoryWithStore(browserHistory, store);

ReactDOM.render(
  <Provider store={store}>
    <Router history={history} routes={routes} />
  </Provider>,
  document.getElementById('root') as HTMLElement
);

而後在src/reducers/index.tsx中添加上routerReducer

import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import { enthusiasm } from './demo';
const rootReducer = combineReducers({
  demo: enthusiasm,
  routing: routerReducer
});

export default rootReducer;

OK,很是簡單。

 

添加開發必備配置

原本是不打算寫一這部分的,畢竟整個基礎環境搭下來就只剩loader部分沒有寫了,而loader配置基本上github對應的庫上都有寫。

可是我本身裝載loader的時候遇到了一些問題,因此這裏寫出來,避免更多的人踩坑了。

css-loader配置

安裝依賴

npm i -D css-loader@0.28.7 style-loader@0.19.0

css-loader用來加載css文件,style-loader用來把加載好的文件放入html中的style標籤裏,因此這兩個loader必須配合使用。

編寫匹配規則,在webpack.common.config.js中的module.rules中添加以下規則:

{ test: /\.css$/, loader: 'style-loader!css-loader' }

而後咱們建立/src/components/hello.css,並輸入如下內容:

.hello{
  background:#000;
}
.greeting{
  color:#fff;
}

而後在/src/components/Hello.tsx中引入:

import './hello.css';

ok,到目前爲止咱們的loader尚未出現問題。

可是你覺得這樣就結束了麼?那是不可能的,否則我寫這部分的目的是什麼,手動滑稽。

如今咱們想要用css modules,不知道什麼是css modules的請點擊CSS Modules 用法教程

因此把webpack.common.config.js中剛剛添加的規則修改爲如下內容:

{ test: /\.css$/, loader: "style-loader!css-loader?modules" }

而後更改下/src/components/Hello.tsx對css的引用

import style from "./hello.css";

而後從新編譯,報錯TS2307: Cannot find module './hello.css'.

什麼鬼?找不到css?驚喜不驚喜? 

順手把問題往谷歌一丟,就能找到別人的也碰到了這個問題,下面也有一些解決方案 在這

看下來大概就是有兩種解決方案:

一、使用typed-css-modules解決。

二、使用require的方式。

我絕不猶豫的選擇了第二種,由於第一種只能解決css的引入問題。那若是我要引入圖片文件呢?因此最終仍是要用require。

這部分若是有其餘解決方案的話,請大神告訴我一下,不甚感激。

這個報錯是typescript報錯,咱們只須要在src目錄下增長index.d.ts文件便可,內容以下:

declare module '*.scss' {
  const content: any
  export default content
}

 

接下來咱們改一下/src/components/Hello.tsx中的引入方式:

const style = require('./hello.css');

若是你是一步步跟着來的話,應該會碰到和我同樣的報錯:error TS2304: Cannot find name 'require'.

這是由於咱們沒有引入@types/node聲明文件,因此咱們須要安裝一下。

npm i -D @types/node@8.0.34

從新編譯一下,終於沒有問題了。

接下來繼續修改/src/components/Hello.tsx,將class部分修改爲css modules的模式

<div className={style.hello}>
    <div className={style.greeting}>
        Hello {name + getExclamationMarks(enthusiasmLevel)}
    </div>
    <div>
        <button onClick={onDecrement}>-</button>
        <button onClick={onIncrement}>+</button>
    </div>
</div>    

保存,ok,樣式生效。

須要注意的是,若是咱們引入了其餘的組件庫,好比antd的話,就不能這樣直接使用css modules,若是想要使用必須配置以下兩條規則:

{ test: /\.css$/, loader: "style-loader!css-loader", include: /node_modules/ },
{ test: /\.css$/, loader: "style-loader!css-loader?modules", exclude: /node_modules/ },

意思應該也都懂了,使用modules的時候須要排除node_modules裏引入的那些庫。

 

file-loader配置

安裝依賴

npm i -D file-loader@1.1.5

在webpack.common.config.js的module.rules中添加規則:

{ test: /\.(png|jpe?g|gif)/, loader: "file-loader" }

而後把在src下建立文件夾img,並放入一張圖片,我這裏是x.png

在/src/components/Hello.tsx中引入圖片,並在JSX中添加一個img標籤,一樣也須要用require引入

//引入
const imgX = require('../img/x.png');

//JSX部分
<div className={style.hello}>
    <div className={style.greeting}>
        Hello {name + getExclamationMarks(enthusiasmLevel)}
    </div>
    <div>
        <button onClick={onDecrement}>-</button>
        <button onClick={onIncrement}>+</button>
     </div>
     <img src={imgX} alt="imgX"/>
</div>

保存,進入localhost:8080,然而圖片並無出現。

可是若是咱們審查元素的話是能看到有img這個元素的,也就是說引入的位置出了問題。咱們以前說過,webpack-dev-server編譯出來的文件是在內存中的,而且目錄是/dist/,可是咱們能夠很明顯的看到咱們img的src值是沒有/dist/目錄的

因此,咱們須要在webpack.common.config.js的output中再加一條屬性 publicPath:'/dist/' ,這個屬性的具體含義請看詳解Webpack2的那些路徑

從新編譯,ok,圖片顯示出來了。

 其餘的經常使用loader,好比babel,postcss我添加的時候沒有遇到什麼問題,因此就不貼出來了,若是有人反饋,我再解答吧。

 

按需加載

typescript + react-router + webpack 實現按需打包/加載

 

結束語

若是在React-Router和React-Router-Redux的配置中有什麼報錯,基本上是npm包的版本問題,請刪除node_modules並按照我指定的版本從新安裝。

總結:安裝過程當中確實碰到了各類各樣的問題,尤爲是Router的包附帶的history版本問題,弄了好久。看似很簡單的教程,背後有我踩過無數的坑,不過好在仍是完成了。

以後還要繼續集成ant-design,以及生產環境的配置,這些都將會在本教程繼續更新。

完整代碼已放入github

 

參考資料:TypeScript-React-Starter

React與webpack | TypeScript Handbook(中文版)

webpack HMR

詳解Webpack2的那些路徑

TypeScript-React-Starter | Issues#29

webpack-dev-server使用react-router browserHistory的配置

相關文章
相關標籤/搜索