以前一段時間工做緣由把精力都放在小程序上,趁如今有點空閒時間,恰好官方文檔也補充完整了,我準備重溫一下 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
引入 antdnode
yarn add antd
首先在\src\style\style.scss
引入UI庫樣式react
@import '~antd/dist/antd.css';
而後咱們開始動手裝飾一下界面,打開\src\page\main.jsx
webpack
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
上面咱們引入了antd的所有樣式,這樣會打包太多沒用到的cssgithub
@import '~antd/dist/antd.css';
因而咱們引入按需加載的插件使用
yarn add babel-plugin-import
這個插件能對antd, antd-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不兼容.
先安裝一下依賴
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 }
這是一個挺好的東西,後續我可能會單獨寫一篇,也可能不寫,咱們先學下怎麼引入項目先.
先安裝依賴
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)
tsconfig.json
編譯TypeScript代碼官方推薦的解析庫是awesome-typescript-loader
,而有些人會使用ts-loader
,二者都能工做,區別在於
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.大概意思就是擁有一流的集成和緩存,能夠跳過多餘的構建減小時間消耗.可以新開進程去處理類型檢查等操做,並行構建項目.
咱們須要在根目錄建立一個tsconfig.json
文件
{ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, "module": "commonjs", "target": "es5", "jsx": "react" }, "include": [ "./src/**/*" ] }
上面屬性即便不解釋應該也能看懂吧
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.json
和config/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
終於情形一片大好,順利打包,直到你打開界面爲止...
看來是按需加載那塊出了問題了.而後繼續搜索資料找到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.