Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

一步步搭建React项目(二):使用webpack配置开发环境 #14

Open
varHarrie opened this issue Mar 1, 2017 · 17 comments
Open

Comments

@varHarrie
Copy link
Owner

varHarrie commented Mar 1, 2017

上一个教程中,我们已经把一个react项目跑起来了,但是怎么看都觉得太过于敷衍,在这次教程中我们将使用webpack配置一个称心的开发环境

本教程webpack版本号为2+

接下来的教程,将在上一个教程项目的基础上完成

主要完成以下改进:

  1. 引入webpack,实现模块管理

  2. 实现监听文件改动,自动编译并刷新浏览器

  3. 实现热替换(HMR)

1. 初步引入webpack:实现模块管理

在根目录下创建webpack.dev.js文件:

const path = require('path')
const root = __dirname

module.exports = {
  // 入口文件
  entry: path.resolve(root, 'src/main.js'),
  // 出口文件
  output: {
    filename: 'bundle.js',
    path: path.resolve(root, 'dist')
  },
  // loaders
  module: {
    rules: [
      {test: /\.jsx?$/, use: ['babel-loader'], exclude: /node_modules/}
    ]
  }
}

这里我们通过webpack去执行babel进行编译,所以将babel的配置抽出到一个文件,根目录下创建.babelrc

{
  "presets": [
    ["es2015", {"modules": false}], // webpack 2 本身已支持es6 module
    "react"
  ]
}

将缺少的包都安装上:

$ npm install --save react react-dom
$ npm install --save-dev webpack babel-cli babel-loader babel-preset-es2015 babel-preset-react

当前的package.json模块如下:

"dependencies": {
  "react": "^15.4.2",
  "react-dom": "^15.4.2"
},
"devDependencies": {
  "babel-cli": "^6.23.0",
  "babel-loader": "^6.3.2",
  "babel-preset-es2015": "^6.22.0",
  "babel-preset-react": "^6.23.0",
  "webpack": "^2.2.1"
}

最后修改一下现有的文件:

  • 改一下index.html的script引入位置:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>React Demo</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="dist/bundle.js"></script>
  </body>
</html>
  • main.js中使用import引入模块
import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('app')
)
  • 修改package.jsonscripts
"scripts": {
  "dev": "webpack --config webpack.dev.js"
}

测试看看,编译之后打开浏览器

$ npm run dev

2. 完善webpack配置:实现监听文件改动,自动编译并刷新浏览器

实现监听文件改动然后自动编译新的bundle.js,我们需要用到webpack-dev-server去创建一个本地服务器,同时,可以结合html-webpack-plugin去生成index.html,先安装:

$ npm install webpack-dev-server html-webpack-plugin --save-dev

先说说html-webpack-plugin的使用

将我们根目录下的index.html改名为template.html,顾名思义,现在作为一个模板,通过插件会在dist中生成一个对应的index.html文件,template.html中去掉多余的东西:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>React Demo</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- js文件会自动插入到这里,无需自己填写 -->
  </body>
</html>

webpack.dev.js中加入html-webpack-plugin的配置:

// 引入html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // ...
  // 其他配置保持不变
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'React Demo',
      template: path.resolve(root, 'template.html')
    })
  ]
}

现在通过npm run dev,就能看到生成的dist/index.html

接下来引入webpack-dev.serverwebpack.dev.js配置修改如下:

module.exports = {
  entry: [
    'webpack-dev-server/client',
    path.resolve(root, 'src/main.js')
  ],
  output: {
    filename: 'bundle.js',
    path: path.resolve(root, 'dist'),
    publicPath: '/'
  },
  // ...
  // 其他配置保持不变
  // ...
  devServer: {
    contentBase: path.resolve(root, 'dist'),
    publicPath: '/',
    port: 8080,
    historyApiFallback: true
  }
}

package.json中的scripts修改如下:

"scripts": {
  "dev": "webpack-dev-server --config webpack.dev.js"
}

通过npm run dev就可以启动一个本地服务器了,只要文件有改动,就会自动刷新浏览器

3. 完善webpack配置:实现热替换(HMR)

自动刷新依然不尽兴,有时候仅仅改动了某个组件的细微地方(改动文案、样式等等),然后导致整个页面刷新了,有些调试步骤又得重新来一次

下面将讲解如何实现react的热替换

实现热替换需要用到react-hot-loader,使用npm安装:

(该教程发布时,需要添加@next才能安装3.x.x版本)

$ npm install --save-dev react-hot-loader@next

更改webpack.dev.js的配置:

// ...
const webpack = require('webpack')

module.exports = {
  entry: [
    'react-hot-loader/patch', // 激活HMR
    'webpack-dev-server/client',
    'webpack/hot/only-dev-server',
    path.resolve(root, 'src/main.js')
  ],
  // ...
  // 其他配置保持不变
  // ...
  devServer: {
    hot: true, // 激活服务器的HMR
    contentBase: path.resolve(root, 'dist'),
    publicPath: '/',
    port: 8080,
    historyApiFallback: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'React Demo',
      template: path.resolve(root, 'template.html')
    }),
    new webpack.HotModuleReplacementPlugin(), // 热替换插件
    new webpack.NamedModulesPlugin() // 执行热替换时打印模块名字
  ]
}

.babelrc也有相应的改动:

{
  "presets": [
    ["es2015", {"modules": false}],
    "react"
  ],
  "plugins": [
    "react-hot-loader/babel" // 添加HMR支持
  ]
}

为了测试热替换是否生效,在src目录添加一个App.js文件,作为根组件:

import React from 'react'

const App = () => (
  <h1>Hello, world!</h1>
)

export default App

main.js中引入并渲染App,同时又一些为支持HMR的改动:

import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './App'

const render = (App) => {
  ReactDOM.render(
    <AppContainer>
      <App />
    </AppContainer>,
    document.getElementById('app')
  )
}

render(App)

if (module.hot) {
  module.hot.accept('./App', () => render(App))
}

重新运行npm run dev,在对App中的Hello, world!进行改动时,页面并不是整个刷新的,至此完成热替换的配置

@Deguang
Copy link

Deguang commented Jul 29, 2017

App.js 中应该是
const App = () => ( return ( <h1>Hello, world!</h1> ) )

@varHarrie
Copy link
Owner Author

@Deguang Arrow Function

@Deguang
Copy link

Deguang commented Jul 31, 2017

@varHarrie 是我本地写错了,你的demo 没问题 👍

@enyobao
Copy link

enyobao commented Sep 4, 2017

Module not found: Error: Can't resolve 'run' in 'D:\work\test-demo\react-demo'
@ multi react-hot-loader/patch webpack-dev-server/client webpack/hot/only-dev-server ./src/main.js run dev
这个报错是什么原因?求大神解救~

@enyobao
Copy link

enyobao commented Sep 4, 2017

@varHarrie 这是main.js的内容,就是按照上面的代码写的~
import React from 'react'
import ReactDOM from 'react-dom'

import { AppContanier } from 'react-hot-loader'
import App from './App'

const render = (App) => {
ReactDOM.render(


,
document.getElementById('app')
)
}

render(App)

if (module.hot) {
module.hot.accept( './App', () => render(App) )
}

@varHarrie
Copy link
Owner Author

@enyobao 你找找看是哪里用到了run这个模块

@enyobao
Copy link

enyobao commented Sep 4, 2017

@varHarrie 解决了是一个单次写错了T_T

@ZJH9Rondo
Copy link

有一个问题:我按照题主的demo配置好之后,没有问题,然后开始做一些改动练习,但是发现对于this的获取出了问题,看了很多这方面的但是不太懂

  import React from 'react'

const MyComponent = React.createClass({
  getInitialState: () => {
    return {liked: false};
  },
  handleClick: (event) => {
    this.setState({liked: !this.state.liked});
  },

  render: () => {
   console.log(this); // 这里this一直指向 function App()
    const text = this.state.liked ? 'liked' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>You {text} this. Click to toggle.</p>
    );
  }
});

const App = () => (
  <MyComponent />
);

export default App

其余代码和题主的一样

@varHarrie
Copy link
Owner Author

@ZJH9Rondo
对于使用React.createClass创建的组件,成员函数使用普通函数而不是箭头函数,即:
render: () => {...} 改为render () {...},其他函数也都改过来试试。

@est7
Copy link

est7 commented Dec 28, 2017

项目和总结都写的很棒,请问一下每次生成的bundle.js/index.js都在哪里,看了下dist文件夹下并没有生成新的文件..

@daoyi7
Copy link

daoyi7 commented Jan 20, 2018

请问如何配置样式规则,比如less这样的

@gwuhaolin
Copy link

《深入浅出 Webpack》是国内第一本系统全面讲解 Webpack 的图书,涵盖了 Webpack 的入门、配置、实战、优化、原理。

@suruifang
Copy link

前面的都可以跑起来,但是到了热替换这里,就报错了
ERROR in ./src/index.js
Module parse failed: Unexpected token (13:4)
You may need an appropriate loader to handle this file type.
const render = (App) => {
ReactDOM.render(
<AppContainer>
<App />
</AppContainer>,
@ multi (webpack)-dev-server/client?http://localhost:3333 webpack/hot/dev-server babel-polyfill react-hot-loader/patch webpack-dev-server/client webpack/hot/only-dev-server ./src/index.js

@solace202
Copy link

没有分号看着很闹心。

@varHarrie varHarrie added this to the Posts milestone Aug 5, 2021
@ass315
Copy link

ass315 commented Sep 1, 2021

2 里 有这个报错
[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.

  • options has an unknown property 'publicPath'. These properties are valid:
    object { allowedHosts?, bonjour?, client?, compress?, devMiddleware?, headers?, historyApiFallback?, host?, hot?, http2?, https?, ipc?,eware?, onListening?, open?, port?, proxy?, setupExitSignals?, static

我把webpack.dev.js
里的 devServer 整个注掉之后 好了,求问为什么

@ass315
Copy link

ass315 commented Sep 1, 2021

3 也有问题
注掉 webpack.dev.js 里的 devServer 里的
contentBase: path.resolve(root, 'dist'),
publicPath: '/',

这两行就好了
怀疑写法和版本有兼容性

@varHarrie
Copy link
Owner Author

@ass315 版本差异导致的,写这篇文章时是webpack@2.x,现在已经是5.x了,很多配置参数都改了,具体要查阅webpack的官方文档了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants