跳到主要内容

编程 | 记与 Webpack 十二小时的奋战

· 阅读需 13 分钟

2021 年 12 月 24 日

Content

这篇必须用中文记录,而且直到目前还没有完全解决,只实现了一个还可以接受的替代方案,背后还有一些东西没弄明白。

先直接说结论:

  1. 如果所有的依赖包都是commonjs形式(使用requiremodule.export),子包的package.json中没有定义type: "module",则 webpack 无论怎么写,基本都不会出错,直接运行webpack --config xxx.webpack.config.js即可
  2. 如果xxx.config.js文件中使用了import xxx from xx或者子包都是用的type: "module",则在项目的package.json中定义一些type: "module",也可以解决问题
  3. 如果出现了commonjsesm混用的情况,则会报错,要么导致Cannot use import statement outside a module,要么导致mjs等等无法导入因为有type: module之类的问题,下面着重说一下在该种情况下的解决方案

我碰到的问题就是,我直接 clone 的 github 上的两个 boilerplate,一个是electron-react-typescript-webpack-boilerplate,还有一个是electron-typescript-react,这两个 boilerplate 虽然配置有些差异,而且一个用到了babel一个没用,但是共同点就是webpack.config.js中都没有使用import语法,webpack的所有依赖也都是commonjs

而我为了将markdown文件在前端渲染出来,虽然我可以直接使用marked或者react-markdown这类 package,但是基于以前nextjs | gatsby等框架的经验,直接基于webpack进行markdown解析体验会比较好(毕竟两个项目都用上webpack了)。

但是问题来了,我在webpack官网中找到加载markdown相关的loadersremark-loader | webpack 章节时,实际使用发现remark-htmlesm,没法直接用,我的webpack会报错。

接着我就经历了 12 个小时的奋斗,尝试了几乎所有能找到的方案,从最基础的改type: "module",到用上babel-register,甚至详细去读了webpackbabel的相关内容,最后不得不都以失败告终。其中应该说最有影响力、提供解决方案最多(然而最终都失败)的帖子应该是下面这个:

问题说的很清楚,就是要在webpack.config.js中使用ES6

但答案都不 OK,最终,我在万念俱灰无数次之后,偶然在 Bug at render-markdown stage of tutorial: Must use import to load ES Module · Discussion #27758 · vercel/next.js 这个帖子里找到了一句话:npm install remark@13 remark-html@13,凭着经验,我一下就知道是怎么回事了,因为前两天我刚刚与万恶的webstorm不支持tailwindcss最新版最终不得不安装tailwindcss@2.0.1-compat做了同样艰苦的斗争!

picture 5

果然,安装指定的版本后,项目终于成功运行:

picture 6

至此,所有与webpack的斗争,应该说告一段落了,不然,我下一个更艰苦卓绝的打算,可能就是去看nextjs之类重型框架的源码,剖析它们是怎么高大上地使用webpack了。。

所以,目前,我可以确定的就是,只要保证所有webpack依赖的库都是commonjs,就可以不借助babel使项目运行,毕竟当一个前端项目需要用上babel的时候,它肯定就已经是另一个项目了,于是,一键 DELETE 恢复世界清净。

picture 9

不过事实上,就像我开头说的,我 clone 了两个项目,其中一个用了 babel,用于转换jsx之类的文件的,它是基于electron-forge的,为了弄清楚当时的报错我还特意去看了这个库,说实话也没怎么弄明白。它的这个boilerplate对我最大的启发,应该是写了一个标准的mainrenderer之间通信的接口,即:

  1. main 中暴露 api 给 renderer 使用的接口定义
// src/@types/bridge.d.ts
import {api} from 'electron/bridge';

declare global {
// eslint-disable-next-line
interface Window {
Main: typeof api;
}
}
  1. main 中对 api 使用的收发封装
// electron/bridge.ts
import {contextBridge, ipcRenderer} from 'electron';

export const api = {
/**
* Here you can expose functions to the renderer process
* so they can interact with the main (electron) side
* without security problems.
*
* The function below can accessed using `window.Main.sendMessage`
*/

sendMessage: (message: string) => {
ipcRenderer.send('message', message);
},

/**
* Provide an easier way to listen to events
*/
on: (channel: string, callback: Function) => {
ipcRenderer.on(channel, (_, data) => callback(data));
},
};

contextBridge.exposeInMainWorld('Main', api);

这两个文件,包括 main 和 render 程序中如何使用这两个文件的写法对我还是有很好的指导作用的,至少 electron 官网上对 typescript 的范式介绍比较少,这就需要从这些 boilerplate 中寻找指点。

但除此之外,这个 boilerplate 对我的意义就不大了,而且,我也不怎么看得懂它给定的几个scripts,其实无法就是开发版、生产版、运行、打包与测试的不同命令,但是被他搞的有点负责,相比较之下,另一个 boilerplate 就很好,清晰明白,也有可能上半年我在学习 electron 的时候正好用的就是第二个 boilerplate,但无论怎样,确实第二个更好。

picture 10
picture 11

但为了确信这一点(2 比 1 好),我又重新去确认了一下 github 的 star 数,结果,并不是这样的,第一个 boilerplate 的 star 有 1.2k,而第二个只有 176,好家伙……我的直觉真不靠谱。

不过这也解释地通,因为第一个是叫electron-typescript-react,第二个叫electron-react-typescript-webpack-boilerplate,表面上后者是前者的具化,因为名词更多。

但事实上,恰恰相反,在electron-typescript-react中反而既有webpack也有babel,其实比另一个涉及的东西还有多……从这个角度想,第一个 boilerplate 星多,应该至少归结于三个点:

  1. 名字短
  2. 加了babel,让使用者有了更多发挥的空间
  3. bridge的接口写的棒,对使用者有更大的价值
  4. ...

Anyway,这大概就是关于目前这个问题的所有思考,和两个 boilerplate 带给我的启发。

接下来,才是工作的重头戏,那就是手上工具顺了,就要立即投身到业务实现上了。

但仍然,我开头说的还有一些问题是没有解决的,罗列如下:

  1. 为什么webpack官网的所有loader的配置都是import导入的,难道他们所有的 dependencies 都是 esm 吗?
  2. 有可能不是,因为我还看到他们的有些 loader 可以使用type: "auto"这种配置,应该就是自动对多种 type 的 js 采取不同的策略智能打包的意思,但这个可以作用于config.js的抬头吗?好像不可以吧,因为我看他们是用在rules里的。
  3. 肯定存在很多commonjsesm混用的项目的,如果不引入babelwebpack是否可以有策略正常运行呢?目前,他们都是基于babel的吗?
  4. 如果是基于babel,我认为我之前尝试装那么多babel依赖,应该也可以正常运行啊,为什么就不能奏效呢?

关于第三点,我突然想到我上半年也写过脚本,翻出来看了一下: picture 12

可以看到,确实是要用babel_register进行子程序注册的,并且我也确实是用import进行导包的: picture 13

人老了啊,记性不行了,当时应该研究这些东西也花了一星期的,结果都一点印象没了,幸好好记性不如烂笔头,整理了当时的代码库。再回想开头看的那个关于babel_register的解决方案,内心突然就有些豁然开朗了: picture 14

不过呢,至少这个项目,我应该暂时不需要了 hhh,如果需要,到时候yarn add一顿乱装,加个babel-register脚本,改一下启动命令行,应该就行了,hhh,肯定不会像今天这样头疼了。

那就暂时先这样,对该问题的记录到此为止。

Former Record

[Problem] SyntaxError: Cannot use import statement outside a module

This discussion is very close to what I happened to, however they gave no solution, except pointed out that there may be some package using es which seems not proper for webpack, and inspired me to have a check at the target package I wanted to import as official webpack suggested me to.

The target package of remark-html I wanted to import

[Failed] Solution of babel-node

It seems that babel-node is a possible solution to this problem, since it when I installed it, it automatically detected what's going wrong of all my webpack loaders.

picture 1

[Failed] Solution of @babel/register

真正唯一的收获

前段时间有个字节的小姐姐,和我表述想转码的意愿,聊下来觉得前端对她可以一试,毕竟目前各种开发岗位里前端是相对来说最容易上手的了……

但是!

这说的只是十二十年前纯粹用html + css + jquery 或者 php的时代!

现在,前端技术突飞猛进,各种范式层出不穷,变得越来越复杂了,三大框架AngularJS | React | Vue就不说了,webpackbabel绝对能让大厂的前端开发掉一层皮……

但 anyway,如果不论这些,平时写写前端真地能让人心情大好,获得感十足,用一位朋友对开发的态度来说:“平时自己写些玩玩挺好的,当做工作就太无聊了”,确实如是~

保持学习,保持热爱! picture 15

reference: