webpack4引入Ant Design和Typescript(四)

前言

以前一段時間工做緣由把精力都放在小程序上,趁如今有點空閒時間,恰好官方文檔也補充完整了,我準備重溫一下 webpack 之路了,由於官方文檔已經寫得很是詳細,我會大量引用原文描述,主要重點放在怎麼從零構建 webpack4 代碼上,這不是一個系統的教程,而是從零摸索一步步搭建起來的筆記,因此前期可能bug會後續發現繼續修復而不是修改文章.javascript

系列文章

webpack4從零開始構建(一)
webpack4+React16項目構建(二)
webpack4功能配置劃分細化(三)
webpack4引入Ant Design和Typescript(四)
webpack4代碼去重,簡化信息和構建優化(五)
webpack4配置Vue版腳手架(六)css

繼續上回分解,咱們以前已經實現了腳手架的雛形,這章就從開發角度搞事情了.回顧以前的示例代碼難以忍受的醜,爲了兼顧界面美觀和開發效率,咱們會引入一些UI庫使用html

2019/03/14上傳,代碼同步到引入antd webpack4_demo_antd
2019/03/15上傳,代碼同步到引入typescript webpack4_demo_typescriptjava

Ant Design React

引入 antdnode

yarn add antd

首先在\src\style\style.scss引入UI庫樣式react

@import '~antd/dist/antd.css';

而後咱們開始動手裝飾一下界面,打開\src\page\main.jsxwebpack

import React, { Component } from "react";
import { Switch, Route, Redirect, Link } from "react-router-dom";
import { hot } from "react-hot-loader";
import View1 from "CMT/view1.jsx";
import View2 from "CMT/view2.jsx";
import "STYLE/style.scss";
import { Layout, Menu } from 'antd';

const { Header, Content, Footer } = Layout;

class Main extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      title: "Hello World!"
    };
  }

  render() {
    return (
      <Layout className="layout">
        <Header>
          <Menu
            theme="dark"
            mode="horizontal"
            defaultSelectedKeys={['1']}
            style={{ lineHeight: '64px' }}
          >
            <Menu.Item key="1"><Link to="/view1/">View1</Link></Menu.Item>
            <Menu.Item key="2"><Link to="/view2/">View2</Link></Menu.Item>
          </Menu>
        </Header>
        <Content style={{ padding: '0 50px' }}>
          <div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
            <h2>{this.state.title}</h2>
            <Switch>
              <Route exact path="/" component={View1} />
              <Route path="/view1/" component={View1} />
              <Route path="/view2/" component={View2} />
              <Redirect to="/" />
            </Switch>
          </div>
        </Content>
        <Footer style={{ textAlign: 'center' }}>
          Ant Design ©2018 Created by Ant UED
        </Footer>
      </Layout>
    )
  }
}

export default hot(module)(Main);

執行命令查看效果ios

npm run prod

界面以下
圖片描述
圖片描述git

按需加載babel-plugin-import

上面咱們引入了antd的所有樣式,這樣會打包太多沒用到的cssgithub

@import '~antd/dist/antd.css';

因而咱們引入按需加載的插件使用

yarn add babel-plugin-import

這個插件能對antdantd-mobile, lodash, material-ui等庫作按需加載

而後咱們將\src\style\style.scss裏的引入樣式刪除

// @import '~antd/dist/antd.css';

.babelrc文件修改以下

{
    "presets": [
        ["env", {
            modules: false
        }], "react"
    ],
    "plugins": ["react-hot-loader/babel", ["import", {
        "libraryName": "antd", // 引入庫名稱
        "libraryDirectory": "lib", // 來源,default: lib
        "style": true, // 所有,or 按需'css'
    }]]
}

效果以下

import { Button } from 'antd';
ReactDOM.render(<Button>xxxx</Button>);

      ↓ ↓ ↓ ↓ ↓ ↓
      
var _button = require('antd/lib/button');
require('antd/lib/button/style/css');
ReactDOM.render(<_button>xxxx</_button>);

實際上就是幫你轉換成對應模塊樣式引入,從新執行命令

npm run prod

控制檯報錯

ERROR in ./node_modules/antd/lib/tooltip/style/index.less 1:0
Module parse failed: Unexpected character '@' (1:0)
You may need an appropriate loader to handle this file type.

@import '../../style/themes/default';
| @import '../../style/mixins/index';
|
@ ./node_modules/antd/lib/tooltip/style/index.js 5:0-23
@ ./node_modules/antd/lib/menu/style/css.js
@ ./src/page/main.jsx
@ ./src/index.js

ERROR in ./node_modules/antd/lib/style/index.less 1:0
Module parse failed: Unexpected character '@' (1:0)
You may need an appropriate loader to handle this file type.

@import './themes/default';
| @import './core/index';
|
@ ./node_modules/antd/lib/tooltip/style/index.js 3:0-33
@ ./node_modules/antd/lib/menu/style/css.js
@ ./src/page/main.jsx
@ ./src/index.js

粗略一看,antd內置使用Less預處理器,和咱們配置的Scss不兼容.

引入LESS

先安裝一下依賴

yarn add less less-loader

而後再config/rules.js新增對Less文件處理,從新執行命令,OK了

{
  test: /antd.*\.less$/, // 匹配文件
  use: [
    process.env.NODE_ENV !== "SERVER"
      ? {
          loader: MiniCssExtractPlugin.loader,
          options: {
            // you can specify a publicPath here
            // by default it use publicPath in webpackOptions.output
            publicPath: process.env.NODE_ENV === "DEV" ? "./" : "../"
          }
        }
      : "style-loader", // 使用<style>將css-loader內部樣式注入到咱們的HTML頁面,
    "css-loader", // 加載.css文件將其轉換爲JS模塊
    {
      loader: "postcss-loader",
      options: {
        config: {
          path: "./" // 寫到目錄便可,文件名強制要求是postcss.config.js
        }
      }
    },
    {
      loader: "less-loader",
      options: {
        javascriptEnabled: true // 是否處理js內樣式
      }
    }
  ]
},

兩個地方須要注意

1, 咱們業務依然保持使用Scss,因此Less只限於引入庫,因此咱們須要限定範圍減小搜索時間

test: /antd.*\.less$/

2, less-loader@3+須要在選項增長對Js引入的less文件處理

options: {
    javascriptEnabled: true // 是否處理js引入less
}

Typescript

這是一個挺好的東西,後續我可能會單獨寫一篇,也可能不寫,咱們先學下怎麼引入項目先.

先安裝依賴

yarn add typescript awesome-typescript-loader source-map-loader

後面若是有遇到這種錯誤那是由於typescript版本過高的bug,能夠嘗試退回到3.1.6版本試試

ERROR in ./src/index.tsx
Module build failed: Error: Final loader (./node_modules/awesome-typescript-loader/dist/entry.js) didn't return a Buffer or String
at runLoaders (C:\work\project\webpack_demo\node_modules\webpack\lib\NormalModule.js:318:18)
at C:\work\project\webpack_demo\node_modules\loader-runner\lib\LoaderRunner.js:370:3
at iterateNormalLoaders (C:\work\project\webpack_demo\node_modules\loader-runner\lib\LoaderRunner.js:211:10)
at iterateNormalLoaders (C:\work\project\webpack_demo\node_modules\loader-runner\lib\LoaderRunner.js:218:10)
at C:\work\project\webpack_demo\node_modules\loader-runner\lib\LoaderRunner.js:233:3
at context.callback (C:\work\project\webpack_demo\node_modules\loader-runner\lib\LoaderRunner.js:111:13)
at process.internalTickCallback (internal/process/next_tick.js:77:7)
  • awesome-typescript-loader可讓Webpack使用TypeScript的標準配置文件 tsconfig.json編譯TypeScript代碼
  • source-map-loader使用TypeScript輸出的sourcemap文件來告訴webpack什麼時候生成 本身的sourcemaps

awesome-typescript-loader

官方推薦的解析庫是awesome-typescript-loader,而有些人會使用ts-loader,二者都能工做,區別在於

  1. atl has first-class integration with Babel and enables caching possibilities. This can be useful for those who use Typescript with Babel. When useBabel and useCache flags are enabled, typescript's emit will be transpiled with Babel and cached. So next time if source file (+environment) has the same checksum we can totally skip typescript's and babel's transpiling. This significantly reduces build time in this scenario.
  2. atl is able to fork type-checker and emitter to a separate process, which also speeds-up some development scenarios (e.g. react with react-hot-loader) So your webpack compilation will end earlier and you can explore compiled version in your browser while your files are typechecked.

大概意思就是擁有一流的集成和緩存,能夠跳過多餘的構建減小時間消耗.可以新開進程去處理類型檢查等操做,並行構建項目.

咱們須要在根目錄建立一個tsconfig.json文件

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

上面屬性即便不解釋應該也能看懂吧

source-map-loader

source-map-loader會從入口的全部js中提取出源映射,包括內聯和URL連接而後傳遞給webpack作處理.對一些擁有本身源映射的第三方庫尤其有用,由於它們可能會引發瀏覽器的曲解.這樣作可以讓webpack去維護源映射的數據連續性,方便調試.

打開config/rules.js新增處理操做

{
  test: /\.(js|jsx)$/, // 匹配文件
  use: ['source-map-loader'],
  enforce: "pre",
  exclude: /node_modules/, // 過濾文件夾
  use: {
    loader: "babel-loader"
  }
},
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
{
  test: /\.tsx?$/,
  loader: "awesome-typescript-loader",
  exclude: [
    /node_modules\/mutationobserver-shim/g,
  ]
},

接下來咱們在webpack.common.js配置一下extensions ,由於可能大部分人再引入文件時候都習慣不補上文件擴展名,這時候webpack就會按照extensions 一個個去匹配,默認 ['.wasm', '.mjs', '.js', '.json']

resolve: {
    // Add '.ts' and '.tsx' as resolvable extensions.
    extensions: [".ts", ".tsx", ".js", ".json"],
    // 建立 import 或 require 的別名,來確保模塊引入變得更簡單
    alias
}

而後咱們開始修改文件後綴,例如src\component\view1.jsx ->src\component\view1.tsx.
如今執行命令

npm run dev

你會驚喜地發現終端狠狠的報錯

ERROR in [at-loader] ./src/component/view1.tsx:1:33
     TS7016: Could not find a declaration file for module 'react'. 'C:/work/project/webpack_demo/node_modules/react/index.js' implicitly has an 'any' type.
   Try `npm install @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react';`

 ERROR in [at-loader] ./src/component/view1.tsx:6:7
     TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.

 ERROR in [at-loader] ./src/component/view1.tsx:6:15
     TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.

 ERROR in [at-loader] ./src/component/view1.tsx:7:7
     TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.

 ERROR in [at-loader] ./src/component/view1.tsx:7:34
     TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i @types/node` and then add `node` to the types field in your tsconfig.

由於咱們還要添加React和React-DOM以及它們的聲明文件到package.json文件裏作爲依賴.

yarn add @types/react @types/react-dom @types/react-router-dom

再次執行命令依然報錯

ERROR in [at-loader] ./src/component/view1.tsx:1:8
     TS1192: Module '"C:/work/project/webpack_demo/node_modules/@types/react/index"' has no default export.

 ERROR in [at-loader] ./src/component/view1.tsx:7:34
     TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i @types/node` and then add `node` to the types field in your tsconfig.

雖然不知道緣由,可是已經不能直接引入React裏的東西,因此咱們還要改一下引入寫法

import React, { Fragment } from "react";

      ↓ ↓ ↓ ↓ ↓ ↓
      
import * as React from "react";
const { Fragment } = React;

後面發現原來tsconfig.json提供了一個選項,那就不用改寫法了.

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true, // 容許從沒有設置默認導出的模塊中默認導入。這並不影響代碼的輸出,僅爲了類型檢查。 
    ...
  },
  ...
}

後面版本問題已經被廢棄了,因此咱們換了個屬性,具體緣由Deprecated 'allowSyntheticDefaultImports' for synthetic modules

"esModuleInterop": true, // 容許從沒有設置默認導出的模塊中默認導入。這並不影響代碼的輸出,僅爲了類型檢查。

你覺得這樣就完了吧,不!!

ERROR in [at-loader] ./src/component/view1.tsx:8:34
     TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i @types/node` and then add `node` to the types field in your tsconfig.

驚喜不驚喜?意外不意外?如今連require圖片資源的語法都出問題了,因而咱們跟着提示繼續安裝依賴

yarn add @types/node

再來一遍執行命令,終於能夠順利運行了,其餘相關文件也所有轉成tsx格式

src\component\view2.jsx ->src\component\view2.tsx

import React, { Fragment } from "react";

export default () => {
  return (
    <Fragment>
      <p>Page2</p>
      <div className="img2" />
    </Fragment>
  );
};

src\page\main.jsx ->src\page\main.tsx

import React, { Component } from "react";
import { Switch, Route, Redirect, Link } from "react-router-dom";
import { hot } from "react-hot-loader";
import View1 from "CMT/view1";
import View2 from "CMT/view2";
import "STYLE/style.scss";
import { Layout, Menu } from 'antd';

const { Header, Content, Footer } = Layout;

class Main extends Component<{}, { title: string }> {
  constructor(props: Object, context: Object) {
    super(props, context);
    this.state = {
      title: "Hello World!"
    };
  }

  render() {
    return (
      <Layout className="layout">
        <Header>
          <Menu
            theme="dark"
            mode="horizontal"
            defaultSelectedKeys={['1']}
            style={{ lineHeight: '64px' }}
          >
            <Menu.Item key="1"><Link to="/view1/">View1</Link></Menu.Item>
            <Menu.Item key="2"><Link to="/view2/">View2</Link></Menu.Item>
          </Menu>
        </Header>
        <Content style={{ padding: '0 50px' }}>
          <div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
            <h2>{this.state.title}</h2>
            <Switch>
              <Route exact path="/" component={View1} />
              <Route path="/view1/" component={View1} />
              <Route path="/view2/" component={View2} />
              <Redirect to="/" />
            </Switch>
          </div>
        </Content>
        <Footer style={{ textAlign: 'center' }}>
          Ant Design ©2018 Created by Ant UED
        </Footer>
      </Layout>
    )
  }
}

export default hot(module)(Main);

src\index.js ->src\index.tsx

import React from "react";
import ReactDOM from "react-dom";
import {
  HashRouter
} from "react-router-dom";
import Main from "PAGE/main";
import "../index.html";

ReactDOM.render(
  <HashRouter>
    <Main />
  </HashRouter>,
  document.getElementById("root")
);

記得要把其餘文件例如package.jsonconfig/webpack.common.js等文件的index引入後綴同步改一下.

到了這步你覺得你成功了,結果又是一個晴天霹靂

ERROR in [at-loader] ./src/index.tsx:6:18
     TS2307: Cannot find module 'PAGE/main'.

 ERROR in [at-loader] ./src/page/main.tsx:4:19
     TS2307: Cannot find module 'CMT/view1'.

 ERROR in [at-loader] ./src/page/main.tsx:5:19
     TS2307: Cannot find module 'CMT/view2'.

由於如今tsx也須要配置本身的一套解析路徑,因而咱們繼續修改tsconfig.json

{
  "compilerOptions": {
    "esModuleInterop": true, // 容許從沒有設置默認導出的模塊中默認導入。這並不影響代碼的輸出,僅爲了類型檢查。
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "jsx": "react",
    "baseUrl": "src", // 解析非相對模塊名的基準目錄
    // 模塊名到基於 baseUrl的路徑映射的列表
    "paths": {
      "@/*": ["*"],
      "IMG/*": ["img/*"],
      "STYLE/*": ["style/*"],
      "JS/*": ["js/*"],
      "ROUTER/*": ["router/*"],
      "PAGE/*": ["page/*"],
      "CMT/*": ["component/*"]
    },
  },
  "include": [
    "./src/*"
  ],
  "exclude": [
    "node_modules",
  ]
}

抱着屢戰屢敗的勇氣再次執行

npm run dev

終於情形一片大好,順利打包,直到你打開界面爲止...
圖片描述

ts-import-plugin

看來是按需加載那塊出了問題了.而後繼續搜索資料找到typescript專用的按需加載庫

yarn add ts-import-plugin

跟着文檔走一個個修改

tsconfig.json

{
  "compilerOptions": {
    "module": "ESNext", // 指定生成哪一個模塊系統代碼: "None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015"。
    ...
  },
  ...
}

config/rules.js

const tsImportPluginFactory = require("ts-import-plugin");
------------------------------------------------------------
{
    test: /\.tsx?$/,
    loader: "awesome-typescript-loader",
    options: {
      useCache: true,
      useBabel: false, // !important!
      getCustomTransformers: () => ({
        before: [tsImportPluginFactory({
          libraryName: 'antd',
          libraryDirectory: 'lib',
          style: true
        })]
      }),
    },
    exclude: [
      /node_modules\/mutationobserver-shim/g,
    ]
  }

繼續執行命令

npm run dev

順利編譯完成,打開頁面一看,嗯,心裏毫無波動~~

main.tsx?21bb:9 Uncaught ReferenceError: antd_1 is not defined
     at Object.eval (main.tsx?21bb:9)
     at eval (main.tsx:58)
     at Object../src/page/main.tsx (main.bundle.js:3209)
     at __webpack_require__ (main.bundle.js:20)
     at eval (index.tsx?22d4:6)
     at Object../src/index.tsx (main.bundle.js:3197)
     at __webpack_require__ (main.bundle.js:20)
     at main.bundle.js:84
     at main.bundle.js:87

繼續埋頭苦幹,各類調查,發現typescript.json還有一個屬性配置

{
  "compilerOptions": {
    "moduleResolution": "node", // 決定如何處理模塊。或者是"Node"對於Node.js/io.js,或者是"Classic"(默認)
    ...
  },
  ...
}

再來一次!!

npm run dev

感謝上帝!!

收尾

由於咱們如今用上typescript以後,有一些東西就能夠直接廢棄了,例如

按需加載babel-plugin-import已經替換成ts-import-plugin

.babelrc還原回到

{
    "presets": [
        ["env", {
            modules: false
        }], "react"
    ],
    "plugins": ["react-hot-loader/babel"]
}

由於typescript自己就支持各類JavaScript版本的轉換,甚至是不一樣的規範 ,因此咱們將js和jsx的相關loader也去掉.

暫時運行起來還沒問題,可是畢竟沒有通過項目實戰,可能有bug.

相關文章
相關標籤/搜索