Webpack的配置案例-devServer、EsLint与多页面

217 阅读4分钟

1. devServer

这里讲解两个devServer相关的配置案例

实现请求转发

问题发现

这里我们基于React尝试发送一个Ajax请求,这里先借助axios库来实现:

axios.get('http://www.joern-lee.com/react/api/header.json').then((res) => {
 ...   
})

这里正常情况下访问一些服务都会遇到跨域问题,另一方面在测试环境下,有时候请求的后端地址其实是不一样的,所以在代码中把host写死的话会很不灵活。

所以通常我们在Ajax都会写一个相对路径而非绝对路径:

axios.get('/react/api/header.json')

但是如何请求到真正的地址呢?一种方法是借助本地类似charles等工具来进行转发。另一种其实可以借助devServer来进行

devServer的proxy

devServer的转发功能主要通过配置proxy字段来实现,例如我们希望将上述/react/api/xxx转发到指定地址,通过proxy就可以解决。

首先我们不使用proxy,直接访问,正常会报如下log:

GET http://localhost:8080/react/api/header.json 404

然后我们这样配置一样:

devServer:{
    ...
    proxy:{
        '/react/api':'http://wwww.joern-lee.com'
    }
}

接着再通过chrome的devtools看一下请求内容,虽然请求的url仍然是localhost,但是经过转发可以获取数据了(上面的proxy地址可以填你自己可以访问的接口测试)

有时候开发阶段,后端会给你一个demo数据的接口来代替header.json,如果你直接改代码,最后上线还要改回来,我们可以通过配置proxy来解决:

proxy:{
     // 如果请求了/react/api这个地址,首先会去http://www.dell-lee.com下面拿数据
     // 那header.json数据时候会拿demo.json返回给你
     // 这样就不用改代码测试,不需要变源代码
        '/react/api':{
            target:'http://wwww.joern-lee.com',
            pathRewrite:{
                'header.json':'demo.json' 
                
            }
        }
    }

更多配置

针对https请求需要加一个secure参数:

proxy:{
        '/react/api':{
            target:'http://wwww.joern-lee.com',
            secure:false
        }
    }

这里我们还可以拦截请求,做一些中间处理:

proxy:{
        '/react/api':{
            target:'http://wwww.joern-lee.com',
            secure:false,
            bypass:function(req,res,proxyOptions){
                process();
            }
        }
    }

希望多个路径进行同一处理:

proxy:[{
    context:['/react/auth','/react/api'],
    target:...
}]

有一些网站对Origin做了限制,防止外部爬虫,这里可以通过这个字段绕过限制:

changeOrigin:true

注意事项

proxy是devServer的代理,只在开发环境下有效,打包上线之后这种代理就不存在了,不会有请求转发!这一点需要注意,主要方便开发环境进行

解决单页面应用路由问题

这里我们以一个React的路由为例来介绍devServer的一个案例

问题引入

这里我们写一个基础的react路由:

...

class App extends Component{
    render(){
        return(
            <BrowserRouter>
                <div>
                    <Route path='/' component={Home}/>
                    <Route path='/list' component={List}/>
                </div>
            </BrowserRouter>
        )
    }
}
...

这里的路由会根据具体的path来决定渲染的组件,以之前的webpack配置文件打包,然后我们看一下浏览器。

正常情况下Home组件可以正常展示,但是path是list时候就不行了。

这是因为BrowserRouter路由器借助了H5 history相关的API,所以当我们在浏览器输入list时,浏览器会尝试访问服务器获取/list下的资源,但其实该路径是不存在的,所以这里也是单页面应用会遇到的一个问题

使用devServer解决

这里只需要加一个historyApiFallback参数就可以了:

devServer:{
    historyApiFallback:true
}

配置的该参数之后,如果浏览器找不到界面就返回默认首页index.html

原理是当服务器发现没有list地址的时候,会偷偷把针对这个路径的请求转化到根路径的请求,你可以发现它其实加载的都是index.html的内容

该参数下面还可以配置from和to字段,来自定义转化的路径:

// 下面配置等价于默认的配置
historyApiFallback:{
    rewrites:[{
        from:/\.*/,
        to:'/index.html'
    }]
}

2. EsLint的配置

简单介绍

团队中,有时希望能够管理大家的编码习惯,使得编程风格尽可能统一,否则同一段逻辑的写法可能都不一样,不好维护。

这里就需要引入EsLint这套代码规范的约束工具

我们可以通过npm进行安装:

npm install eslint --save-dev

之后还需要配置eslint来配置具体规范,这里可以输入命令来初始化:

npx eslint --init

然后根据控制台的配置流程来帮助你生成配置文件.eslintrc.js

eslint具体的使用和配置这里就不展开说了,一般来说可以结合babel-eslint解析器,或者给你的IDE安装插件来使用

但是如果你是通过在IDE装插件才完成ESLint高亮展示,此时其他开发者没有安装该插件,那他怎么看到代码语法提示呢?

所以单纯靠插件来确保质量会有问题(独立开发就没关系了),你没办法每个人都确保安装了插件。使用命令行工具又很麻烦。

这里就可以在webpack中把esLint整合进去

webpack中使用

这里我们可以安装一个loader来处理:

npm install eslint-loader --save-dev

然后修改webapck配置文件:

rules:[...,{
    test:/\.js$/,
    exclude:/node_modules/,
    use:['babel-loader','eslint-loader']
}}}]

这里注意顺序,首先通过eslint-loader来规范代码,然后再进行代码转义。如果转义再检查那么就不对了

需要配置devServer一个配置,设置overlay,这样就可以在不符合规范时弹窗进行提示,这里就把两者结合起来:

devServer:{
    overlay:true
}

接着存在违反eslint的代码编写时,就会在浏览器上弹出一个浮层提示异常。

即便编辑器没有插件也可以很快定义问题。

另外还有cache配置,可以介绍每一次打包时候解析文件时候损耗的速度,提高一点打包速度

最佳实践

公司里面使用EsLint通常不会使用eslint-loader,因为会影响打包速度。

通常在准备提交代码到git仓库的时候,通过git的一些钩子对代码进行eslint检查,执行eslint src,如果不通过规范,禁止你提交代码。

如果对打包速度影响较大,可以扩展一些其他思路,即便牺牲一些便捷性

3. 多页面打包配置案例

之前的打包基本都是针对SPA应用进行,整个页面只有一个HTML文件,这主要是考虑到现在主流前端开发都是基于react,vue等的单页面应用。

但是在部分情况下,例如对老项目兼容,就可能需要处理多页面的打包了

实验环境搭建

我们新建一个首页和列表页:

....

class App extends Component{
    ...
    <div>home page</div>
}

ReactDom.render(<App/>,document.getElementById('root'));
....

class App extends Component{
    ...
    <div>list page</div>
}

ReactDom.render(<App/>,document.getElementById('root'));

基于多页面应用那么上述两个组件就不是父子关系,而是用于两个HTML文件的root节点,这里我们需要先修改配置文件以打包这两个JS文件:

module.exports = {
    entry:{
        main:'./src/index.js',
        list:'./src/list.js'
    }
}

打包完成后,dist目录下面会分别有list和main的打包文件,但是目前打包生成的html文件还是只有一个(通过两个script标签引入了组件)

生成多个HTML文件

这里我们希望生成多个HTML文件且各自分别引用一个JS文件,这里就可以借助前述的HtmlWebpackPlugin插件,新增一个打包产出的HTML

这里我们可以去github上面看一些这个插件文档,多配置一个Plugin:

const plugins = [{
    new HtmlWebpackPlugin({
        template:'src/index.html',
        filename:'index.html',
        chunks:['runtime','vendors','main']
    }),
    new HtmlWebpackPlugin({
        template:'src/index.html',
        filename:'list.html',
        chunks:['runtime','vendors','main']
    })
    new CleanWebpackPlugin...
}]

上述配置了两个Html插件,用于处理多个html文件,模板html都是同一个,filename决定了打包后的文件名称,chunks决定了html引入的包文件。

然后重新打包就可以看到html正确引入了对应的JS文件

自动化生成多HTMLPlugin

上述过程如果涉及HTML很多的话我们需要写很多new HtmlWebpackPlugin的模板代码,这里我们可以写一个解析函数,根据entry字段来生成plugin数组:

const makePlugins = (configs) =>{
    const plugins = [
        new CleanWebpackPlugin...
    ];
    Object.keys(configs.entry).forEach(item => {
        new HtmlWebapckPlugin({
            template:'src/index.html',
            filename:`${item}.html`,
            chunks:['runtime','vendors',item]
        })
    })
}

当你需要新增一个页面,只需要改config配置文件的entry字段就可以